Phoenix API Toolkit v0.12.0-beta.1 PhoenixApiToolkit.Security.Plugs View Source

Security-related plugs.

Several of these plugs are based on recommendations for API's by the OWASP guidelines.

Link to this section Summary

Functions

DEPRECATED. Use set_forwarded_ip/2 instead.

Adds security headers to the response as recommended for API's by OWASP. Sets "x-frame-options": "deny" and "x-content-type-options": "nosniff".

Checks if the request's "content-type" header is present. Content matching is done by Plug.Parsers.

Set conn.remote_ip to the value in header "x-forwarded-for", if present.

Check if the JWT in conn.assigns.jwt has an "aud" claim that matches the exp_aud parameter. This assign is set by PhoenixApiToolkit.Security.Oauth2Plug and should contain a JOSE.JWT struct.

Check if the JWT in conn.assigns.jwt has a "scope" claim that matches the exp_scopes parameter. This assign is set by PhoenixApiToolkit.Security.Oauth2Plug and should contain a JOSE.JWT struct.

Link to this section Functions

Link to this function

assign_client_ip(conn, opts \\ [])

View Source
assign_client_ip(Plug.Conn.t(), Plug.opts()) :: Plug.Conn.t()
This function is deprecated. Use `&set_forwarded_ip/2` instead..

DEPRECATED. Use set_forwarded_ip/2 instead.

Assigns the client's IP to the conn as client_ip. Prefers IP in header "x-forwarded-for" over the directly detected remote IP.

Examples

use Plug.Test

def conn_with_ip, do: conn(:get, "/") |> Map.put(:remote_ip, {127, 0, 0, 12})

# by default, the value of `remote_ip` is used
iex> (conn_with_ip() |> assign_client_ip()).assigns.client_ip
"127.0.0.12"

# if header "x-forwarded-for" is set, its value is preferred
iex> (conn_with_ip() |> put_req_header("x-forwarded-for", "10.0.0.1") |> assign_client_ip()).assigns.client_ip
"10.0.0.1"
Link to this function

put_security_headers(conn, opts \\ [])

View Source
put_security_headers(Plug.Conn.t(), Plug.opts()) :: Plug.Conn.t()

Adds security headers to the response as recommended for API's by OWASP. Sets "x-frame-options": "deny" and "x-content-type-options": "nosniff".

Examples

use Plug.Test

# it does what it says it does
iex> conn = conn(:get, "/")
iex> put_security_headers(conn).resp_headers -- conn.resp_headers
[{"x-frame-options", "deny"}, {"x-content-type-options", "nosniff"}]
Link to this function

require_content_type(conn, opts \\ [])

View Source
require_content_type(Plug.Conn.t(), Plug.opts()) :: Plug.Conn.t()

Checks if the request's "content-type" header is present. Content matching is done by Plug.Parsers.

The filter is only applied to methods which are expected to carry contents, to PUT, POST and PATCH methods, that is. Only one content-type header is allowed. A noncompliant request causes a PhoenixApiToolkit.Security.MissingContentTypeError to be raised, resulting in a 415 Unsupported Media Type response.

Examples

use Plug.Test

# safe methods pass through
iex> conn = conn(:get, "/")
iex> conn == require_content_type(conn)
true

# compliant unsafe methods (put, post and patch) pass through
iex> conn = conn(:post, "/") |> put_req_header("content-type", "application/json")
iex> conn == require_content_type(conn)
true

# noncompliant unsafe methods cause a MissingContentTypeError to be raised
iex> conn(:post, "/") |> require_content_type()
** (PhoenixApiToolkit.Security.MissingContentTypeError) missing 'content-type' header
Link to this function

set_forwarded_ip(conn, opts \\ [])

View Source
set_forwarded_ip(Plug.Conn.t(), Plug.opts()) :: Plug.Conn.t()

Set conn.remote_ip to the value in header "x-forwarded-for", if present.

## Examples

use Plug.Test

def conn_with_ip, do: conn(:get, "/") |> Map.put(:remote_ip, {127, 0, 0, 12})

# by default, the value of `remote_ip` is left alone
iex> conn = conn_with_ip() |> set_forwarded_ip()
iex> conn.remote_ip
{127, 0, 0, 12}

# if header "x-forwarded-for" is set, remote ip is overwritten
iex> conn = conn_with_ip() |> put_req_header("x-forwarded-for", "10.0.0.1") |> set_forwarded_ip()
iex> conn.remote_ip
{10, 0, 0, 1}
Link to this function

verify_oauth2_aud(conn, exp_aud)

View Source
verify_oauth2_aud(Plug.Conn.t(), binary()) :: Plug.Conn.t()

Check if the JWT in conn.assigns.jwt has an "aud" claim that matches the exp_aud parameter. This assign is set by PhoenixApiToolkit.Security.Oauth2Plug and should contain a JOSE.JWT struct.

If not, a PhoenixApiToolkit.Security.Oauth2TokenVerificationError is raised, resulting in a 401 Unauthorized response.

Examples

use Plug.Test

def conn_with_aud(aud), do: conn(:get, "/") |> assign(:jwt, %{fields: %{"aud", aud}})

# if aud matches, the conn is passed through
iex> conn = conn_with_aud("my resource server")
iex> conn == conn |> verify_oauth2_aud("my resource server")
true

# an error is raised if aud does not match
iex> conn_with_aud("my resource server") |> verify_oauth2_aud("another server")
** (PhoenixApiToolkit.Security.Oauth2TokenVerificationError) Oauth2 token invalid: aud mismatch
Link to this function

verify_oauth2_scope(conn, exp_scopes)

View Source
verify_oauth2_scope(Plug.Conn.t(), [binary()]) :: Plug.Conn.t()

Check if the JWT in conn.assigns.jwt has a "scope" claim that matches the exp_scopes parameter. This assign is set by PhoenixApiToolkit.Security.Oauth2Plug and should contain a JOSE.JWT struct.

If not, a PhoenixApiToolkit.Security.Oauth2TokenVerificationError is raised, resulting in a 401 Unauthorized response.

Examples

use Plug.Test

def conn_with_scope(scope), do: conn(:get, "/") |> assign(:jwt, %{fields: %{"scope", scope}})

# if there is a matching scope, the conn is passed through
iex> conn = conn_with_scope("admin read:phone")
iex> conn == conn |> verify_oauth2_scope(["admin"])
true
iex> conn == conn |> verify_oauth2_scope(["admin", "not:a:match"])
true
iex> conn == conn |> verify_oauth2_scope(["admin", "read:phone"])
true

# an error is raised if there is no matching scope
iex> conn_with_scope("admin read:phone") |> verify_oauth2_scope(["not:a:match"])
** (PhoenixApiToolkit.Security.Oauth2TokenVerificationError) Oauth2 token invalid: scope mismatch