JWT Authentication¶
Authentication Source¶
Before configuring a JWT middleware, a JWT Authentication Source must be defined in the static configuration.
Below is an example of a minimal JWT Authentication Source that can be added to a static configuration:
authSources:
jwtSource:
jwt:
signingSecret: super-secret
[authSources]
[authSources.jwtSource]
[authSources.jwtSource.jwt]
signingSecret = "super-secret"
Note
To use base64-encoded signingSecret
s, set the signingSecretBase64Encoded
option to true.
Authentication Source Options¶
signingSecret
¶
Optional (one of signingSecret
, publicKey
, jwksFile
or jwksUrl
must be set), Default=""
The signingSecret
option can be set to the secret used for signing the JWT certificates. It is then used by the middleware to verify incoming requests.
authSources:
jwtSource:
jwt:
signingSecret: super-secret
[authSources]
[authSources.jwtSource]
[authSources.jwtSource.jwt]
signingSecret = "super-secret"
signingSecretBase64Encoded
¶
Optional, Default=false
The signingSecretBase64Encoded
option can be set to indicate that the signingSecret
is base64-encoded. If set to true, the signingSecret
is base64-decoded before being used.
authSources:
jwtSource:
jwt:
signingSecret: c3VwZXItc2VjcmV0Cg==
signingSecretBase64Encoded: true
[authSources]
[authSources.jwtSource]
[authSources.jwtSource.jwt]
signingSecret = "c3VwZXItc2VjcmV0Cg=="
signingSecretBase64Encoded = true
publicKey
¶
Optional (one of signingSecret
, publicKey
, jwksFile
or jwksUrl
must be set), Default=""
The publicKey
option can be used as an alternative to a signing secret to verify incoming requests. In that case, users should sign their token using a private key, and the public key can be used to verify the signature.
authSources:
jwtSource:
jwt:
publicKey: |-
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv
vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc
aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy
tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0
e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb
V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9
MwIDAQAB
-----END PUBLIC KEY-----
[authSources]
[authSources.jwtSource]
[authSources.jwtSource.jwt]
publicKey = """
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv
vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc
aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy
tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0
e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb
V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9
MwIDAQAB
-----END PUBLIC KEY-----
"""
jwksFile
¶
Optional (one of signingSecret
, publicKey
, jwksFile
or jwksUrl
must be set), Default=""
The jwksFile
option can be used to define a set of JWK to be used to verify the signature of JWTs. More information on JWK can be found in the reference.
The option can either be a path to a file mounted on the proxies or directly the content of a JWK set file.
authSources:
jwtSource:
jwt:
jwksFile: /etc/config/jwks.json
authSources:
jwtSource:
jwt:
jwksFile: |-
{
"keys": [
{
"use": "sig",
"kty": "EC",
"kid": "key-id",
"crv": "P-256",
"alg": "ES256",
"x": "EVs_o5-uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf84",
"y": "kGe5DgSIycKp8w9aJmoHhB1sB3QTugfnRWm5nU_TzsY"
}
]
}
[authSources]
[authSources.jwtSource]
[authSources.jwtSource.jwt]
jwksFile = /etc/config/jwks.json
[authSources]
[authSources.jwtSource]
[authSources.jwtSource.jwt]
jwksFile = """
{
"keys": [
{
"use": "sig",
"kty": "EC",
"kid": "key-id",
"crv": "P-256",
"alg": "ES256",
"x": "EVs_o5-uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf84",
"y": "kGe5DgSIycKp8w9aJmoHhB1sB3QTugfnRWm5nU_TzsY"
}
]
}
"""
JWT Header Key ID
If the JWT header contains a kid
header, the middleware expects to find a JWK. If a
JWK cannot be found, it returns a 401 Unauthorized
error.
jwksUrl
¶
Optional (one of signingSecret
, publicKey
, jwksFile
or jwksUrl
must be set), Default=""
The jwksUrl
option can be used as an alternative to a signing secret to verify incoming requests.
It is the URL of the host serving a JWK set. More information on JWK can be found in the reference.
The keys are cached if the HTTP Cache Control allows for caching.
This option can either be set to a full URL (e.g.: https://www.googleapis.com/oauth2/v3/certs
) or to a path (e.g.: /oauth2/v3/certs
). In the first case, the middleware simply fetches the keys located at the URL to verify the token. In the second case, the middleware builds the full URL using the iss
property found in the JWT claims. It does so by concatenating the host defined by iss
and the path set by jwksURL
.
authSources:
jwtSource:
jwt:
jwksUrl: https://www.googleapis.com/oauth2/v3/certs
[authSources]
[authSources.jwtSource]
[authSources.jwtSource.jwt]
jwksUrl = "https://www.googleapis.com/oauth2/v3/certs"
JWT Header Key ID
If the JWT header contains a kid
header, the middleware expects to find a JWK. If a
JWK cannot be found, it returns a 401 Unauthorized
error.
JWT Issuer Claim
If jwksUrl
is set to a path and the iss
property is missing in the JWT it's trying to verify, the middleware returns a 401 Unauthorized
error.
tls
¶
Optional
Defines the TLS configuration used to secure the connection to the JWK server.
tls.caBundle
¶
Optional, Default=""
An optional caBundle
containing a PEM-encoded certificate bundle or a path to a file containing the certificate bundle
used to establish a TLS connection with the JWK server.
Using a File
Note that TraefikEE does not watch for file changes.
If caBundle
is set to a file path, its content will be read once when the middleware is initialized.
authSources:
jwtSource:
jwt:
tls:
caBundle: |-
-----BEGIN CERTIFICATE-----
MIIB9TCCAWACAQAwgbgxGTAXBgNVBAoMEFF1b1ZhZGlzIExpbWl0ZWQxHDAaBgNV
BAsME0RvY3VtZW50IERlcGFydG1lbnQxOTA3BgNVBAMMMFdoeSBhcmUgeW91IGRl
Y29kaW5nIG1lPyAgVGhpcyBpcyBvbmx5IGEgdGVzdCEhITERMA8GA1UEBwwISGFt
aWx0b24xETAPBgNVBAgMCFBlbWJyb2tlMQswCQYDVQQGEwJCTTEPMA0GCSqGSIb3
DQEJARYAMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCJ9WRanG/fUvcfKiGl
EL4aRLjGt537mZ28UU9/3eiJeJznNSOuNLnF+hmabAu7H0LT4K7EdqfF+XUZW/2j
RKRYcvOUDGF9A7OjW7UfKk1In3+6QDCi7X34RE161jqoaJjrm/T18TOKcgkkhRzE
apQnIDm0Ea/HVzX/PiSOGuertwIDAQABMAsGCSqGSIb3DQEBBQOBgQBzMJdAV4QP
Awel8LzGx5uMOshezF/KfP67wJ93UW+N7zXY6AwPgoLj4Kjw+WtU684JL8Dtr9FX
ozakE+8p06BpxegR4BR3FMHf6p+0jQxUEAkAyb/mVgm66TyghDGC6/YkiKoZptXQ
98TwDIK/39WEB/V607As+KoYazQG8drorw==
-----END CERTIFICATE-----
authSources:
jwtSource:
jwt:
tls:
caBundle: /etc/tls/ca-bundle.pem
[authSources]
[authSources.jwtSource]
[authSources.jwtSource.jwt.tls]
caBundle = """
-----BEGIN CERTIFICATE-----
MIIB9TCCAWACAQAwgbgxGTAXBgNVBAoMEFF1b1ZhZGlzIExpbWl0ZWQxHDAaBgNV
BAsME0RvY3VtZW50IERlcGFydG1lbnQxOTA3BgNVBAMMMFdoeSBhcmUgeW91IGRl
Y29kaW5nIG1lPyAgVGhpcyBpcyBvbmx5IGEgdGVzdCEhITERMA8GA1UEBwwISGFt
aWx0b24xETAPBgNVBAgMCFBlbWJyb2tlMQswCQYDVQQGEwJCTTEPMA0GCSqGSIb3
DQEJARYAMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCJ9WRanG/fUvcfKiGl
EL4aRLjGt537mZ28UU9/3eiJeJznNSOuNLnF+hmabAu7H0LT4K7EdqfF+XUZW/2j
RKRYcvOUDGF9A7OjW7UfKk1In3+6QDCi7X34RE161jqoaJjrm/T18TOKcgkkhRzE
apQnIDm0Ea/HVzX/PiSOGuertwIDAQABMAsGCSqGSIb3DQEBBQOBgQBzMJdAV4QP
Awel8LzGx5uMOshezF/KfP67wJ93UW+N7zXY6AwPgoLj4Kjw+WtU684JL8Dtr9FX
ozakE+8p06BpxegR4BR3FMHf6p+0jQxUEAkAyb/mVgm66TyghDGC6/YkiKoZptXQ
98TwDIK/39WEB/V607As+KoYazQG8drorw==
-----END CERTIFICATE-----
"""
[authSources]
[authSources.jwtSource]
[authSources.jwtSource.jwt.tls]
caBundle = "/etc/tls/ca-bundle.pem"
tls.insecureSkipVerify
¶
Optional, Default=false
Disables TLS certificate verification when communicating with the JWK server. Useful for testing purposes but strongly discouraged for production.
authSources:
jwtSource:
jwt:
tls:
insecureSkipVerify: true
[authSources]
[authSources.jwtSource]
[authSources.jwtSource.jwt.tls]
insecureSkipVerify = true
timeout
¶
Optional, Default="5s"
This option controls the time before giving up requests to the JWK server.
authSources:
jwtSource:
jwt:
timeout: 15s
[authSources]
[authSources.jwtSource.jwt]
timeout = "15s"
maxRetries
¶
Optional, Default=3
The number of retries for requests to the JWK server that fail.
authSources:
jwtSource:
jwt:
maxRetries: 5
[authSources]
[authSources.jwtSource.jwt]
maxRetries = 5
JWT Middleware¶
After declaring a JWT Authentication Source in the static configuration of the cluster, JWT middlewares can be added to routers in the dynamic configuration.
The JWT middleware verifies that a token is provided in the Authorization
header (Authorization: Bearer <JWT>
) of incoming requests or in the query parameters of the request (jwt=<JWT>
).
JWT Query Parameter
The JWT query parameter is configurable with the tokenQueryKey
option.
With no specific configuration, a JWT middleware only validates the signature of a JWT and checks the nbf
, exp
and iat
standard claims (if they are present). Custom claim validation can be configured with Custom Claims Validation.
Middleware Options¶
source
¶
Required, Default=""
The source
option should contain the name of the Authentication Source used by the middleware.
labels:
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.source=jwtSource"
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: test-jwtAuth
spec:
plugin:
jwtAuth:
source: jwtSource
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.source=jwtSource"
"labels": {
"traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.source": "jwtSource"
}
labels:
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.source=jwtSource"
http:
middlewares:
test-jwt:
plugin:
jwtAuth:
source: jwtSource
[http.middlewares]
[http.middlewares.test-jwtAuth.plugin.jwtAuth]
source = "jwtSource"
forwardAuthorization
¶
Optional, Default=false
The forwardAuthorization
option determines if the authorization headers have to be forwarded or stripped from a request after it has been approved by the middleware.
labels:
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.forwardAuthorization=true"
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: test-jwtAuth
spec:
plugin:
jwtAuth:
forwardAuthorization: true
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.forwardAuthorization=true"
"labels": {
"traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.forwardAuthorization": "true"
}
labels:
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.forwardAuthorization=true"
http:
middlewares:
test-jwt:
plugin:
jwtAuth:
forwardAuthorization: true
[http.middlewares]
[http.middlewares.test-jwtAuth.plugin.jwtAuth]
forwardAuthorization = true
forwardHeaders
¶
Optional, Default=None
The forwardHeaders
option sets the HTTP headers to add to requests and populates them with claim values extracted from a JWT.
Note
Claims to be forwarded that are not found in the JWT result in empty headers.
labels:
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.forwardHeaders.Group=grp"
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.forwardHeaders.Expires-At=exp"
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: test-jwtAuth
spec:
plugin:
jwtAuth:
forwardHeaders:
Group: grp
Expires-At: exp
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.forwardHeaders.Group=grp"
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.forwardHeaders.Expires-At=exp"
"labels": {
"traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.forwardHeaders.Group": "grp",
"traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.forwardHeaders.Expires-At": "exp"
}
labels:
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.forwardHeaders.Group=grp"
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.forwardHeaders.Expires-At=exp"
http:
middlewares:
test-jwt:
plugin:
jwtAuth:
forwardHeaders:
Group: grp
Expires-At: exp
[http.middlewares]
[http.middlewares.test-jwtAuth.plugin.jwtAuth]
[http.middlewares.test-jwtAuth.plugin.jwtAuth.forwardHeaders]
Group = "grp"
Expires-At = "exp"
username
¶
Optional, Default=""
The username
option sets the claim that will be evaluated to populate the clientusername
in the accessLog.
labels:
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.username=userId"
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: test-jwtAuth
spec:
plugin:
jwtAuth:
username: userId
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.username=userId"
"labels": {
"traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.username": "userId"
}
labels:
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.username=userId"
http:
middlewares:
test-jwt:
plugin:
jwtAuth:
username: userId
[http.middlewares]
[http.middlewares.test-jwtAuth.plugin.jwtAuth]
username = userId
tokenQueryKey
¶
Optional, Default=""
The tokenQueryKey
sets the middleware to look for the token to use in a specific query parameter if not found in the Authorization
header. The middleware always look in the Authorization
header first.
labels:
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.tokenQueryKey=tok"
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: test-jwtAuth
spec:
plugin:
jwtAuth:
tokenQueryKey: tok
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.tokenQueryKey=tok"
"labels": {
"traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.tokenQueryKey": "tok"
}
labels:
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.tokenQueryKey=tok"
http:
middlewares:
test-jwtAuth:
plugin:
jwtAuth:
tokenQueryKey: tok
[http.middlewares]
[http.middlewares.test-jwtAuth.plugin.jwtAuth]
tokenQueryKey = "tok"
claims
¶
Optional, Default=""
The claims
option sets claims to validate in order to authorize the request.
labels:
- "traefik.http.middlewares.test-jwt-auth.plugin.jwtAuth.claims=Equals(`grp`, `admin`)"
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: test-jwt-auth
spec:
plugin:
jwtAuth:
claims: Equals(`grp`, `admin`)
- "traefik.http.middlewares.test-jwt-auth.plugin.jwtAuth.claims=Equals(`grp`, `admin`)"
"labels": {
"traefik.http.middlewares.test-jwt-auth.plugin.jwtAuth.claims": "Equals(`grp`, `admin`)"
}
labels:
- "traefik.http.middlewares.test-jwt-auth.plugin.jwtAuth.claims=Equals(`grp`, `admin`)"
http:
middlewares:
test-jwt-auth:
plugin:
jwtAuth:
claims: Equals(`grp`, `admin`)
[http.middlewares]
[http.middlewares.test-jwt-auth.plugin.jwtAuth]
claims = "Equals(`grp`, `admin`)"
Syntax¶
The following functions are supported in claims
:
Function | Description | Example |
---|---|---|
Equals | Validated the equality of the value in key with value . |
Equals(`grp`, `admin`) |
Prefix | Validates the value in key has the prefix of value . |
Prefix(`referrer`, `http://example.com`) |
Contains (string) | Validates the value in key contains value . |
Contains(`referrer`, `/foo/`) |
Contains (array) | Validates the key array contains the value . |
Contains(`areas`, `home`) |
SplitContains | Validates the value in key contains the value once split by the separator. |
SplitContains(`scope`, ` `, `writer`) |
OneOf | Validates the key array contains one of the values . |
OneOf(`areas`, `office`, `lab`) |
All functions can be joined by boolean operands. The supported operands are:
Operand | Description | Example |
---|---|---|
&& | Compares two functions and returns true only if both evaluate to true. | Equals(`grp`, `admin`) && Equals(`active`, `true`) |
|| | Compares two functions and returns true if either evaluate to true. | Equals(`grp`, `admin`) || Equals(`active`, `true`) |
! | Returns false if the function is true, otherwise returns true. | !Equals(`grp`, `testers`) |
All examples returns true for the following data structure:
{
"active": true,
"grp": "admin",
"scope": "reader writer deploy",
"referrer": "http://example.com/foo/bar",
"areas": [
"office",
"home"
]
}
Nested claims¶
Nested claims are supported by using a .
between keys. For example:
user.name
{
"active": true,
"grp": "admin",
"scope": "reader writer deploy",
"referrer": "http://example.com/foo/bar",
"areas": [
"office",
"home"
],
"user" {
"name": "John Snow",
"status": "undead"
}
}
John Snow
Handling keys that contain a '.'
If the key
contains a dot, the dot can be escaped using \.
.
Handling a key that contains a '\'
If the key
contains a \
, it needs to be doubled \\
.
Advanced Configuration Example¶
Below is an advanced configuration example using custom claims validation and forward headers:
labels:
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.source=jwtSource"
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.forwardHeaders.Group=grp"
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.forwardHeaders.Expires-At=exp"
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.claims: Equals(`grp`, `admin`)"
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: test-jwtAuth
spec:
plugin:
jwtAuth:
source: jwtSource
forwardHeaders:
Group: grp
Expires-At: exp
claims: Equals(`grp`, `admin`)
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.source=jwtSource"
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.forwardHeaders.Group=grp"
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.forwardHeaders.Expires-At=exp"
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.claims: Equals(`grp`, `admin`)"
"labels": {
"traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.source": "jwtSource",
"traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.forwardHeaders.Group": "grp",
"traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.forwardHeaders.Expires-At": "exp",
"traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.claims": "Equals(`grp`, `admin`)"
}
labels:
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.source=jwtSource"
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.forwardHeaders.Group=grp"
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.forwardHeaders.Expires-At=exp"
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.claims: Equals(`grp`, `admin`)"
http:
middlewares:
test-jwt:
plugin:
jwtAuth:
source: jwtSource
forwardHeaders:
Group: grp
Expires-At: exp
claims: Equals(`grp`, `admin`)
[http.middlewares]
[http.middlewares.test-jwtAuth.plugin.jwtAuth]
source = "jwtSource"
claims = "Equals(`grp`, `admin`)"
[http.middlewares.test-jwtAuth.plugin.jwtAuth.forwardHeaders]
Group = "grp"
Expires-At = "exp"