Skip to content

Instantly share code, notes, and snippets.

@haproxytechblog
Last active September 16, 2022 19:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save haproxytechblog/74079ba7df75e3095cc3f872f8b560e4 to your computer and use it in GitHub Desktop.
Save haproxytechblog/74079ba7df75e3095cc3f872f8b560e4 to your computer and use it in GitHub Desktop.
Verify OAuth JWT tokens with HAProxy
$ openssl x509 -pubkey -noout -in ./myaccount.pem > pubkey.pem
$ curl --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"}'
{
"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp...",
"scope":"read:myapp write:myapp",
"expires_in":86400,
"token_type":"Bearer"
}
{
"alg": "RS256",
"typ": "JWT"
}
{
"iss": "https://myaccount.auth0.com/",
"aud": "https://api.mywebsite.com",
"exp": 1662753594,
"scope": "read write",
"gty": "client-credentials"
}
{
// RSASHA256 signature
}
frontend myapi
bind :80
bind :443 ssl crt /etc/haproxy/certs/foo.com/cert.crt alpn h2
http-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 JWT
http-request set-var(txn.alg) http_auth_bearer,jwt_header_query('$.alg')
# get payload part of the JWT
http-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 JWT
http-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,"/etc/haproxy/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 }
http-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 servers
backend servers
balance roundrobin
server web1 192.168.56.31:3000 check maxconn 30
$ curl --request GET \
-k \
--url https://192.168.56.20/ \
--header 'authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp...'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment