APIacAuthBearer (apiac_auth_bearer v2.0.0) View Source
An APIac.Authenticator
plug for API authentication using the OAuth2 Bearer
scheme
The OAuth2 Bearer
scheme is documented in
RFC6750 - The OAuth 2.0 Authorization Framework: Bearer Token Usage
and consists in sending an OAuth2 access token in the HTTP request. Any party
in possession of that token can use it on the API, hence its name: 'Bearer'.
GET /api/accounts HTTP/1.1
Host: example.com
Authorization: Bearer NLdtYEY8Y4Q09kKBUnsYy9mExGQnBy
Accept: */*
That bearer token has been granted beforehand by an OAuth2 authorization server to the client making the API request (typically through one of the 4 RFC6749 flows or one of the 3 OpenID Connect flows).
Note that according to the specification, the bearer can be sent:
- in the
Authorization
HTTP header - in the request body (assuming the request has one)
- as a query parameter
The bearer_extract_methods
plug option allows to specify where to seek the bearer.
Bearer tokens are usually:
- opaque tokens, to be validated against the OAuth2 authorization server that has released it
- self-contained signed JWT tokens, that can be verified locally by the API
Validating the access token
This plug provides with 2 bearer verification implementations:
APIacAuthBearer.Validator.Introspect
which implements RFC7662 - OAuth 2.0 Token Introspection, and which consists in requesting validation of the token on the authorization server that has issued itAPIacAuthBearer.Validator.JWT
which implements JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens and which consists in locally verifying signed (and possibly encrypted) tokens, using the cryptographic keys of the authorization server and of the current API (using this plug)
A validator must implement the APIacAuthBearer.Validator
behaviour.
Caching
A bearer token may be used many times on an API in a short time-frame,
which is why caching is important
when using APIacAuthBearer.Validator.Introspect
or a similar mechanism as a
back pressure mechanism for the authorization server. This plug comes with 4 caching
implementations:
Caching implementation | Repository | Use-case |
---|---|---|
APIacAuthBearer.Cache.NoCache | Built-in | No caching, for testing purpose or when using a custom validator that doesn't require caching |
APIacAuthBearer.Cache.ETSMock | Built-in | Local cache in ETS table, for testing purpose, development environment, etc. Does not have a token expiration clean-up code: the cache will grow endlessly |
APIacAuthBearerCacheCachex | github | Production ready cache, for a single instance or a small cluster of nodes |
APIacAuthBearerCacheRiak | Work in progress | Production ready cache, for larger clusters of nodes |
A cache implements the APIacAuthBearer.Cache
behaviour.
Validation flow sequence diagram
Plug options
realm
: a mandatoryString.t
that conforms to the HTTP quoted-string syntax, however without the surrounding quotes (which will be added automatically when needed). Defaults todefault_realm
bearer_validator
: a{validator_module, validator_options}
tuple wherevalidator_module
is a module implementing theAPIacAuthBearer.Validator
behaviour andvalidator_options
module-specific options that will be passed to the validator when called. No default value, mandatory parameterbearer_extract_methods
: a list of methods that will be tried to extract the bearer token, among:header
,:body
and:query
. Methods will be tried in the list order. Defaults to[:header]
set_error_response
: function called when authentication failed. Defaults toAPIacAuthBearer.send_error_response/3
error_response_verbosity
: one of:debug
,:normal
or:minimal
. Defaults to:normal
required_scopes
: a list of scopes required to access this API. Defaults to[]
. When the bearer's granted scope are not sufficient, an HTTP 403 response is sent with theinsufficient_scope
RFC6750 errorforward_bearer
: if set totrue
, the bearer is saved in thePlug.Conn
APIac metadata (under the "bearer" key) and can be later be retrieved usingAPIac.metadata/1
. Defaults tofalse
forward_metadata
: in addition to the bearer'sclient
andsubject
, list of the validator's response to set in the APIac metadata, or the:all
atom to forward all of the response's data. For example:["username", "aud"]
. Defaults to[]
resource_indicator
: the name of the resource server as a String, to be checked against theaud
attribute returned by the validator. This is an optional security mecanism for RFC7662 and mandatory for JWT access tokens. See the security consideration sections. Defaults tonil
, i.e. no check of this parametercache
: a{cache_module, cache_options}
tuple wherecache_module
is a module implementing theAPIacAuthBearer.Cache
behaviour andcache_options
module-specific options that will be passed to the cache when called. The cached entry expiration ttl can be set thanks to the:ttl
option. It is set to 200 seconds by default, but is shortened when the bearer's lifetime is less than 200 seconds (as indicated by its expiration timestamp of the"exp"
member of bearer metadata returned by the validator) Defaults to{APIacAuthBearer.Cache.NoCache, [ttl: 200]}
Error responses
This plug, conforming to RFC6750, responds with the following status and parameters
in case of authentication failure when :error_response_verbosity
is set to :normal
:
Error | HTTP status | Included WWW-Authenticate parameters |
---|---|---|
No bearer token found | 401 | - realm |
Invalid bearer | 401 | - realm<br>- error |
Bearer doesn't have the required scopes | 403 | - realm<br>- error<br>- scope |
For other :error_response_verbosity
values, see the documentation of the
send_error_response/3
function.
Example
plug APIacAuthBearer, bearer_validator: {
APIacAuthBearer.Validator.Introspect,
[
issuer: "https://example.com/auth"
tesla_middleware:[
{Tesla.Middleware.BasicAuth, [username: "client_id_123", password: "WN2P3Ci+meSLtVipc1EZhbFm2oZyMgWIx/ygQhngFbo"]}
]
]},
bearer_extract_methods: [:header, :body],
required_scopes: ["article:write", "comments:moderate"],
forward_bearer: true,
resource_indicator: "https://example.com/api/data"
cache: {APIacAuthBearerCacheCachex, []}
Security considerations
HTTPS
As the bearer token is sent in an HTTP header, use of HTTPS is mandatory (but however not verfified by this Plug).
Bearer methods
As stated by RFC6750, section 2:
This section defines three methods of sending bearer access tokens in resource requests to resource servers. Clients MUST NOT use more than one method to transmit the token in each request.
This plug does not check whether several methods are used or not. It will
only deal with the first bearer (valid or not) found following the order
of the bearer_extract_methods
.
Form-Encoded Body Parameter
RFC6750, section 2.2, demands that the following conditions are met for form-encoded body bearer access token:
o The HTTP request entity-header includes the "Content-Type" header field set to "application/x-www-form-urlencoded".
o The entity-body follows the encoding requirements of the "application/x-www-form-urlencoded" content-type as defined by HTML 4.01 [W3C.REC-html401-19991224].
o The HTTP request entity-body is single-part.
o The content to be encoded in the entity-body MUST consist entirely of ASCII [USASCII] characters.
o The HTTP request method is one for which the request-body has defined semantics. In particular, this means that the "GET" method MUST NOT be used.
This plug, however:
- doesn't verify that the HTTP request entity-body is single-part
- the content is entirely US-ASCCI (the plug parser checks that it is utf8
Audience
RFC6750, section 5.2, states that:
To deal with token redirect, it is important for the authorization server to include the identity of the intended recipients (the audience), typically a single resource server (or a list of resource servers), in the token. Restricting the use of the token to a specific scope is also RECOMMENDED.
Consider implementing it using the resource_indicator
parameter when using the
RFC7662 introspection validator.
URI Query Parameter
According to RFC6750, section 2.3,:
Clients using the URI Query Parameter method SHOULD also send a Cache-Control header containing the "no-store" option. Server success (2XX status) responses to these requests SHOULD contain a Cache-Control header with the "private" option.
This plug does set the cache-control
to private
when such a method
is used. Beware, however, of not overwriting it later unless you
know what you're doing.
Link to this section Summary
Functions
APIac.Authenticator
credential extractor callback
Saves failure in a Plug.Conn.t()
's private field and returns the conn
Implementation of the APIac.Authenticator
callback
Sets the HTTP WWW-authenticate
header when no such a scheme is used for
authentication.
Link to this section Types
Specs
bearer() :: String.t()
Link to this section Functions
APIac.Authenticator
credential extractor callback
Returns the credentials under the form String.t()
which
is the bearer token
Specs
save_authentication_failure_response( Plug.Conn.t(), %APIac.Authenticator.Unauthorized{ __exception__: term(), authenticator: term(), reason: term() }, any() ) :: Plug.Conn.t()
Saves failure in a Plug.Conn.t()
's private field and returns the conn
See the APIac.AuthFailureResponseData
module for more information.
Implementation of the APIac.Authenticator
callback
Verbosity
The following elements in the HTTP response are set depending on the value
of the :error_response_verbosity
option:
:error_response_verbosity
set to :debug
:
Error | HTTP status | Included WWW-Authenticate parameters |
---|---|---|
No bearer token found | 401 | - realm |
Invalid bearer | 401 | - realm<br>- error<br>- error_description |
Bearer doesn't have the required scopes | 403 | - realm<br>- error<br>- scope<br>- error_description |
:error_response_verbosity
set to :normal
:
Error | HTTP status | Included WWW-Authenticate parameters |
---|---|---|
No bearer token found | 401 | - realm |
Invalid bearer | 401 | - realm<br>- error |
Bearer doesn't have the required scopes | 403 | - realm<br>- error<br>- scope |
:error_response_verbosity
set to :minimal
:
Error | HTTP status | Included WWW-Authenticate parameters |
---|---|---|
No bearer token found | 401 | |
Invalid bearer | 401 | |
Bearer doesn't have the required scopes | 401 |
Note: does not conform to the specification
Specs
set_WWWauthenticate_header( Plug.Conn.t(), %APIac.Authenticator.Unauthorized{ __exception__: term(), authenticator: term(), reason: term() }, any() ) :: Plug.Conn.t()
Sets the HTTP WWW-authenticate
header when no such a scheme is used for
authentication.
Sets the HTTP WWW-Authenticate
header with the Bearer
scheme and the realm
name, when the Bearer
scheme was not used in the request. When this scheme is
used in the request, response will be sent by Elixir.APIacAuthBearer.send_error_response/3
.
This allows advertising that the Bearer
scheme is available, without stopping
the plug pipeline.
Raises an exception when the error response verbosity is set to :minimal
since
it does not set the WWW-Authenticate
header.