AuthN / authZ
OAuth 2.0 authorization
Available since
- HAProxy 2.5
- HAProxy Enterprise 2.5r1
- HAProxy ALOHA 14.0
With the OAuth 2.0 authorization protocol, you can use JSON Web Tokens (JWTs) to convey a client’s level of access for a service without requiring a password.
JWTs, which contain a client’s permissions but not their identity, serve as a client’s proof of membership and encapsulate the fine-grained permissions they have. A JWT is a payload of base64-encoded JSON data, cryptographically signed by the party that authenticated the client. You can require that each HTTP request come with a JWT to verify whether the requested action should be allowed.
The load balancer provides configuration directives that cover all of the functionality needed to support OAuth 2.0, including checking that a token:
- has not expired.
- was issued and signed by a trusted authentication service.
- is meant for your service and not someone else’s.
- contains any necessary claims to grant a client a specific type of access (for example, read or write access).
The load balancer, which sits in front of your service, verifies that the token is genuine and checks it to see which permissions the client should have. You can either have the proxy relay those permissions to your application via HTTP headers or make a decision within the load balancer itself to deny the request immediately. For example, if the client requests to update data with a PUT request, but they do not have the write
permission, you can deny the request.
Get a JWT with Auth0 Jump to heading
The scenario we’ll describe here is a client-side application using OAuth to access a server-side API.
The client-side application will need a JWT to get access to the API. To get the JWT, they must make a request to a third-party authentication service, passing their client ID and client secret to get a JWT in return. Once they have the JWT, they will attach it to their requests that pass through the load balancer in order to gain access.
The load balancer does not generate tokens, so you need to subscribe to an authentication service. Below, we use Auth0 as the authentication service.
To get a key and token using Auth0, follow these steps:
-
Create an account with Auth0.
-
Log into your Auth0 account and go to Applications > APIs to create your API. This represents the API for which you’ll require a JWT. When creating your API, note that the
Identifier
field will be the audience in the token (more on that later) and is typically a URL likehttps://api.mywebsite.com
. TheSigning Algorithm
can beRS256
orHS256
. The former uses an X.509 key pair to verify the signature, while the latter uses a shared secret. -
In Auth0, go to Applications > Applications to create a Machine to Machine Applications that will be calling your API. When creating the application, choose the API it should have access to and its permissions.
-
Go to Applications > Applications > [Your App] > Quick Start to see how to make a call to get an access token.
For example:
nixcurl --request POST \--url https://myaccount.auth0.com/oauth/token \--header 'content-type: application/json' \--data '{"client_id":"abcd12345….","client_secret":"ABCD12345…","audience":"https://api.mywebsite.com","grant_type":"client_credentials"}'nixcurl --request POST \--url https://myaccount.auth0.com/oauth/token \--header 'content-type: application/json' \--data '{"client_id":"abcd12345….","client_secret":"ABCD12345…","audience":"https://api.mywebsite.com","grant_type":"client_credentials"}'outputjson{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp...","scope":"read:myapp write:myapp","expires_in":86400,"token_type":"Bearer"}outputjson{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp...","scope":"read:myapp write:myapp","expires_in":86400,"token_type":"Bearer"}This response contains a JWT access token as the
access_token
field inside a JSON response, which expires after 24 hours. The client should attach this to its HTTP requests via anAuthentication
header.What's inside a JWT?
To see what’s inside the token, check out jwt.io, which can display the fields inside the token.
For example:
json{"alg": "RS256","typ": "JWT"}{"iss": "https://myaccount.auth0.com/","aud": "https://api.mywebsite.com","exp": 1662753594,"scope": "read write","gty": "client-credentials"}{// RSASHA256 signature}json{"alg": "RS256","typ": "JWT"}{"iss": "https://myaccount.auth0.com/","aud": "https://api.mywebsite.com","exp": 1662753594,"scope": "read write","gty": "client-credentials"}{// RSASHA256 signature}The token contains three parts:
- a header
- a payload
- a cryptographic signature
The header indicates which algorithm was used to sign the token. The payload contains the name of the issuer, the intended audience, the expiration date, and any permissions (also known as scopes).
Configure the load balancer with RS256 Jump to heading
To enable the load balancer to validate requests with attached JWTs using the RS256 signing algorithm:
-
Log into your Auth0 account. Go to Applications > Applications > [Your App] > Settings > Advanced Settings > Certificates and download the certificate in the PEM format. Extract the public key from the downloaded PEM file using the
openssl x509
command:nixopenssl x509 -pubkey -noout -in ./myaccount.pem > pubkey.pemnixopenssl x509 -pubkey -noout -in ./myaccount.pem > pubkey.pemStore the public key file on your load balancer server.
-
Update your load balancer configuration as shown here:
haproxyfrontend myapibind :80bind :443 ssl crt /etc/hapee-2.9/certs/foo.com/cert.crt alpn h2http-request redirect scheme https unless { ssl_fc }http-request deny content-type 'text/html' string 'Missing Authorization HTTP header' unless { req.hdr(authorization) -m found }# get header part of the JWThttp-request set-var(txn.alg) http_auth_bearer,jwt_header_query('$.alg')# get payload part of the JWThttp-request set-var(txn.iss) http_auth_bearer,jwt_payload_query('$.iss')http-request set-var(txn.aud) http_auth_bearer,jwt_payload_query('$.aud')http-request set-var(txn.exp) http_auth_bearer,jwt_payload_query('$.exp','int')http-request set-var(txn.scope) http_auth_bearer,jwt_payload_query('$.scope')# Validate the JWThttp-request deny content-type 'text/html' string 'Unsupported JWT signing algorithm' unless { var(txn.alg) -m str RS256 }http-request deny content-type 'text/html' string 'Invalid JWT issuer' unless { var(txn.iss) -m str https://myaccount.auth0.com/ }http-request deny content-type 'text/html' string 'Invalid JWT audience' unless { var(txn.aud) -m str https://api.mywebsite.com }http-request deny content-type 'text/html' string 'Invalid JWT signature' unless { http_auth_bearer,jwt_verify(txn.alg,"/pubkey.pem") -m int 1 }http-request set-var(txn.now) date()http-request deny content-type 'text/html' string 'JWT has expired' if { var(txn.exp),sub(txn.now) -m int lt 0 }# OPTIONAL: Deny requests that lack sufficient permissionshttp-request deny if { path_beg /api/ } { method GET } ! { var(txn.scope) -m sub read }http-request deny if { path_beg /api/ } { method DELETE POST PUT } ! { var(txn.scope) -m sub write }default_backend serversbackend serversbalance roundrobinserver web1 192.168.56.31:3000 check maxconn 30haproxyfrontend myapibind :80bind :443 ssl crt /etc/hapee-2.9/certs/foo.com/cert.crt alpn h2http-request redirect scheme https unless { ssl_fc }http-request deny content-type 'text/html' string 'Missing Authorization HTTP header' unless { req.hdr(authorization) -m found }# get header part of the JWThttp-request set-var(txn.alg) http_auth_bearer,jwt_header_query('$.alg')# get payload part of the JWThttp-request set-var(txn.iss) http_auth_bearer,jwt_payload_query('$.iss')http-request set-var(txn.aud) http_auth_bearer,jwt_payload_query('$.aud')http-request set-var(txn.exp) http_auth_bearer,jwt_payload_query('$.exp','int')http-request set-var(txn.scope) http_auth_bearer,jwt_payload_query('$.scope')# Validate the JWThttp-request deny content-type 'text/html' string 'Unsupported JWT signing algorithm' unless { var(txn.alg) -m str RS256 }http-request deny content-type 'text/html' string 'Invalid JWT issuer' unless { var(txn.iss) -m str https://myaccount.auth0.com/ }http-request deny content-type 'text/html' string 'Invalid JWT audience' unless { var(txn.aud) -m str https://api.mywebsite.com }http-request deny content-type 'text/html' string 'Invalid JWT signature' unless { http_auth_bearer,jwt_verify(txn.alg,"/pubkey.pem") -m int 1 }http-request set-var(txn.now) date()http-request deny content-type 'text/html' string 'JWT has expired' if { var(txn.exp),sub(txn.now) -m int lt 0 }# OPTIONAL: Deny requests that lack sufficient permissionshttp-request deny if { path_beg /api/ } { method GET } ! { var(txn.scope) -m sub read }http-request deny if { path_beg /api/ } { method DELETE POST PUT } ! { var(txn.scope) -m sub write }default_backend serversbackend serversbalance roundrobinserver web1 192.168.56.31:3000 check maxconn 30In this example:
- We get the token from the
Authentication
HTTP header by using thehttp_auth_bearer
fetch method. Thejwt_header_query
converter then extracts fields from the token’s header, and thejwt_payload_query
converter extracts fields from the payload. - We store the fields in variables by using the
http-request set-var
directive. - We use the
http-request deny
directive to deny requests that fail validation in some way. This directive allows you to set a string parameter, which lets you return an error message to the user. - We validate that the JWT’s fields and signature are valid for the expected issuer, audience, and signing algorithm.
- We verify the token’s signature using the
jwt_verify
converter. - We relay valid requests to the backend named
servers
.
- We get the token from the
Different authentication services may use different names for the fields in the token, such as the Identifier
and scopes
fields cited above. Inspect the token or consult the authentication service’s documentation to determine the proper field names.
Test it by calling your load balanced API at the address where the load balancer is listening. In your request, the token should be set in the Authorization
header:
nix
curl --request GET \-k \--url https://192.168.56.20/ \--header 'authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp...'
nix
curl --request GET \-k \--url https://192.168.56.20/ \--header 'authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp...'
See also Jump to heading
Do you have any suggestions on how we can improve the content of this page?