Authentication
As introduced in Overview, nanopie provides pluggable solutions for authentication, in the form of authentication handlers. When added to an endpoint or a service, these handlers validate incoming requests automatically.
The validation of requests is based on the credentials that come
with the requests, such as API keys, user credentials, and bearer JWTs.
An authentication handler first extracts credentials from requests using
a credential extractor, then validate them using a credential validator.
Depending on the authentication scheme you use, some authentication handlers
have preconfigured credential extractors and validators, while others may
need custom ones. In addition, authentication handlers provide the
before_authentication and after_authentication functions,
which allows you to configure how handlers validate requests at runtime; for
example, you may rotate the key used for credential signature verification
for certain credentials, or perform some additional, custom checks on
credentials that are not supported yet in the handlers.
At this moment, nanopie provides the following authentication handlers:
| Service Type | Authentication Scheme | Authentication Handler |
|---|---|---|
| HTTP | API keys | HTTPAPIKeyAuthenticationHandler |
| HTTP | HTTP Basic (RFC6717) | HTTPAPIKeyAuthenticationHandler |
| HTTP | HTTP OAuth2 Bearer Token (RFC 6750) + JWT (RFC 7519) | HTTPOAuthBearerJWTAuthenticationHandler |
Using authentication handlers
Authentication handlers for HTTP services
Authentication with API keys
To authenticate requests in your HTTP microservices/API backends with API
keys, add an HTTPAPIKeyAuthenticationHandler to your service. To use this
authentication handler, you must specify where the API keys reside, and
provide it with a credential validator that validates the API key.
To create a credential validator, inherit from the CredentialValidator class
and override the validate method, which takes a Key credential as input:
from nanopie import CredentialValidator
from nanopie.misc.errors import AuthenticationError
from nanopie.auth.http_api_key import INVALID_KEY_RESPONSE
class KeyCredentialValidator(CredentialValidator):
def validate(self, credential):
# The `Key` credential has only one attribute, `key`, which is the
# API key included in the request
if is_correct(credential.key):
pass
else:
# Raise an AuthenticationError with an HTTP response when the
# authentication fails; nanopie will return the attached response
# to the client
raise AuthenticationError(
"The API key is not correct.",
response=INVALID_KEY_RESPONSE)
credential_validator = KeyCredentialValidator()
Note
See Exceptions for instructions on how to customize the response authentication handlers return to clients when an error occurs.
Next, add the credential validator to the HTTP API key authentication handler.
If your API key resides in the HTTP headers with the name api_key, create an
HTTPAPIKeyAuthenticationHandler with the mode set to HEADER and the
key_field_name to api_key; on the other hand, if your API key resides
in the URI query arguments with the same name, use the mode URI_QUERY
instead.
from nanopie import HTTPAPIKeyAuthenticationHandler
authentication_handler = HTTPAPIKeyAuthenticationHandler(
mode="HEADER",
key_field_name="api_key",
# The credential validator you just created
credential_validator=credential_validator
)
Note
The available modes are listed in nanopie.HTTPAPIKeyModes. Instead of
the raw values, you may also use HTTPAPIKeyModes.HEADER and
HTTPAPIKeyModes.URI_QUERY.
Finally, add the authentication to your endpoint or service. For instructions, see Services; the code snippet below showcases how to add an authentication handler to a Flask microservice:
from nanopie import FlaskService
app = Flase(__name__)
svc = FlaskService(app=app,
# The authentication handler you just created
authn_hanlder=authentication_handler)
Authentication with HTTP Basic scheme
To authenticate requests in your HTTP microservices/API backends with the
HTTP Basic scheme, add an HTTPBasicAuthenticationHandler to your service.
To use this authentication handler, you must provide it with a
credential validator that validates the user credentials that comes
with the requests.
To create a credential validator, inherit from the CredentialValidator class
and override the validate method, which takes a UserCredential
credential as input:
from nanopie import CredentialValidator
from nanopie.auth.http_basic import INVALID_CREDENTIAL_RESPONSE
class UserCredentialValidator(CredentialValidator):
def validate(self, credential):
# The `UserCredential` credential has two attributes,
# `username` and `password`, which are extracted from the Auth header
# in the HTTP request; nanopie performs Base64 decoding on them
# automatically
if is_correct(credential.username, credential.password):
pass
else:
# Raise an AuthenticationError with an HTTP response when the
# authentication fails; nanopie will return the attached response
# to the client
raise AuthenticationError(
"The API key is not correct.",
response=INVALID_CREDENTIAL_RESPONSE)
credential_validator = UserCredentialValidator()
Note
See Exceptions for instructions on how to customize the response authentication handlers return to clients when an error occurs.
Next, add the credential validator to the HTTP Basic authentication handler:
from nanopie import HTTPBasicAuthenticationHandler
authentication_handler = HTTPBasicAuthenticationHandler(
# The credential validator you just created
credential_validator=credential_validator
)
Finally, add the authentication to your endpoint or service. For instructions, see Services; the code snippet below showcases how to add an authentication handler to a Flask microservice:
from nanopie import FlaskService
app = Flase(__name__)
svc = FlaskService(app=app,
# The authentication handler you just created
authn_hanlder=authentication_handler)
Authentication with HTTP OAuth2 Bearer Token scheme and JWTs
To authenticate requests in your HTTP microservices/API backend with the
HTTP OAuth2 Bearer Token + JWT scheme, add an
HTTPOAuth2BearerJWTAuthenticationHandler to your service. This
authentication handler is capable of extracting and validating credentials
(JWTs) by itself; all you need to do is to specify where the bearer token
resides, plus the algorithm and key or secret used to sign JWTs.
Note
This authentication handler supports the following algorithms:
HS256,HS384, orHS512(HMAC with SHA-256/SHA-384/SHA-512)RS256,RS384, orRS512(RSA with SHA-256/SHA-384/SHA-512)ES256,ES384, orES512(ECDSA with SHA-256/SHA-384/SHA-512)PS256,PS384, orPS512(PSS with SHA-256/SHA-384/SHA-512)
As specified in RFC 6750, nanopie
supports extraction of bearer tokens from two places: the headers of HTTP
requests, or the URI query arguments of HTTP requests. If the former is true,
create an HTTPOAuth2BearerJWTAuthenticator with the mode set to HEADER;
this is also default mode HTTPOAuth2BearerJWTAuthenticator will use.
Otherwise, set the mode to URI_QUERY.
from nanopie import HTTPOAuth2BearerJWTAuthenticationHandler
authentication_handler = HTTPOAuth2BearerJWTAuthenticationHandler(
mode="HEADER",
key_or_secret="YOUR-KEY-OR-SECRET",
algorithm="YOUR-ALGORITHM"
)
Note
The available modes are listed in nanopie.HTTPOAuth2BearerJWTModes.
Instead of the raw values, you may also use
HTTPOAuth2BearerJWTModes.HEADER and
HTTPOAuth2BearerJWTModes.URI_QUERY.
Next, add the authentication to your endpoint or service. For instructions, see Services; the code snippet below showcases how to add an authentication handler to a Flask microservice:
from nanopie import FlaskService
app = Flase(__name__)
svc = FlaskService(app=app,
# The authentication handler you just created
authn_hanlder=authentication_handler)
Note that this authentication handler also provides a number of options that allows you to fine-tune the verification of JWTs: you may specify the following keyword arguments when initializing the handler:
| Option | Description |
|---|---|
use_pycrypto |
If set to True, use the pycrypto package for encryption/decryption instead of the default cryptography package. This package only supports RS algorithms. |
use_ecdsa |
If set to True, use the ecdsa package for encryption/decryption instead of the default cryptography package. This package only supports ES algorithms. |
verify_signature |
If set to False, the JWT signature will not be validated. Defaults to True. |
verify_exp |
If set to False, the exp (expiration) claim of the JWT (if any) will not be validated. Defaults to True. |
verify_nbf |
If set to False, the nbf (not before) claim of the JWT (if any) will not be validated. Defaults to True. |
verify_iat |
If set to False, the iat (issued at) claim of the JWT (if any) will not be validated. Defaults to True. |
verify_aud |
If set to True, the aud (audience) claim of the JWT will be validated. Defaults to False. |
verify_iss |
If set to True, the iss (issuer) claim of the JWT will be validated. Defaults to False. |
require_exp |
If set to True, the exp (expiration) claim must be present in the JWT. Defaults to False. |
require_iat |
If set to True, the iat (issued at) claim must be present in the JWT. Defaults to False. |
require_nbf |
If set to True, the nbf (not before) claim must be present in the JWT. Defaults to False. |
audience |
The expected audience of the JWT. |
issuer |
The expected issuer of the JWT. |
leeway |
The margin of error for the exp (expiration) claim. Defaults to 0. |
The instructions so far use a static configuration for JWT verification; in
production environments, it is often required to verify JWTs dynamically, i.e.
using different algorithms and key/secrets for different JWTs. It is also
common for developers to inspect additional, custom fields in the JWT for
further security checks. Both use cases can be
achieved in nanopie using the before_authentication and after_authentication
methods; see the section below for more information.
before_authentication and after_authentication functions
Authentication handlers in nanopie includes two additional decorator methods,
before_authentication and after_authentication, which allows developers
to perform operations before and after the actual authentication workflow.
before_authentication decorates a method that is invoked before the
credential is verified but after the credential is extracted. The method
must accept exactly two arguments, which are:
auth_handler: The running authentication handlercredential: The credential extracted (but not verified yet)
And this method should return None or a credential validator. If the latter
is the case, the running authentication handler will use the returned
credential validator, instead of the one specified at the time of compilation,
to validate credentials.
before_authentication is perfect for setting up dynamic credential validation.
The code snippet below, for example, configures an
HTTPOAuth2BearerJWTAuthenticator to verify JWTs using the algorithm
and public key claimed within the JWTs themselves:
from nanopie import HTTPOAuth2BearerJWTAuthenticationHandler
# This is the class of the default JWT validator in the HTTP OAuth2 Bearer
# Token w/ JWT authentication handler
from nanopie.auth.http_oauth2_bearer_jwt import HTTPOAuth2BearerJWTValidator
authentication_handler = HTTPOAuth2BearerJWTAuthenticationHandler(
mode="HEADER",
key_or_secret="DEFAULT-KEY-OR-SECRET",
algorithm="DEFAULT-ALGORITHM"
)
@authentication_handler.before_authentication
def before_authentication(self, auth_handler, credential):
algorithm = credential.headers.get('algorithm')
public_key = credential.headers.get('public_key')
# Use the default algorithm and key if the JWT does not specify them
if not algorithm:
algorithm = auth_handler.alg
if not public_key:
public_key = auth_handler.pk
# This is the options used for fine tuning JWT verification
kwargs = auth_handler.kwargs
return HTTPOAuth2BearerJWTValidator(key_or_secret=key_or_secret,
algorithm=algorithm,
**kwargs)
after_authentication, on the other hand, decorates a method that is invoked
after the credential is verified. This method also accepts the two arguments
listed earlier and should always return None.
after_authentication is perfect for setting up additional checks on
credentials. The code snippet below, for example, configures an
HTTPOAuth2BearerJWTAuthenticator to verify a custom claim in the JWT
payload:
from nanopie import HTTPOAuth2BearerJWTAuthenticationHandler
from nanopie.misc.errors import AuthenticationError
from nanopie.auth.http_oauth2_bearer_jwt import INVALID_TOKEN_RESPONSE
authentication_handler = HTTPOAuth2BearerJWTAuthenticationHandler(
mode="HEADER",
key_or_secret="DEFAULT-KEY-OR-SECRET",
algorithm="DEFAULT-ALGORITHM"
)
@authentication_handler.after_authentication
def after_authentication(self, auth_handler, credential):
custom_claim = credential.payload.get('custom')
if not custom_claim or not is_correct(custom_claim):
raise AuthenticationError("The custom claim is not correct",
response=INVALID_TOKEN_RESPONSE)
Note
See Exceptions for instructions on how to customize the response authentication handlers return to clients when an error occurs.