Skip to content

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 signingSecrets, 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>). If the token can't be passed as an Authorization header, it can be given as form data or as a query parameter. See the tokenKey option for more information.

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 (deprecated)

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.

Deprecated

This option is deprecated, please use tokenKey instead.

labels:
  - "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.tokenQueryKey=access_token"
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: test-jwtAuth
spec:
  plugin:
    jwtAuth:
      tokenQueryKey: access_token
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.tokenQueryKey=access_token"
"labels": {
  "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.tokenQueryKey": "access_token"
}
labels:
  - "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.tokenQueryKey=access_token"
http:
  middlewares:
    test-jwtAuth:
      plugin:
        jwtAuth:
          tokenQueryKey: access_token
[http.middlewares]
  [http.middlewares.test-jwtAuth.plugin.jwtAuth]
    tokenQueryKey = "access_token"

tokenKey

Optional, Default=""

The tokenKey option allows passing the JWT as a form value or a query parameter for applications that can't pass it in the Authorization header. The middleware always look in the Authorization header first, even with this option enabled. It then looks in query parameters and finally in form data for a value named as configured by tokenKey.

RFC Recommendations

This option should only be enabled if the JWT cannot be passed as an Authorization header as it is not recommended by the RFC.

labels:
  - "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.tokenKey=access_token"
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: test-jwtAuth
spec:
  plugin:
    jwtAuth:
      tokenKey: access_token
- "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.tokenKey=access_token"
"labels": {
  "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.tokenKey": "access_token"
}
labels:
  - "traefik.http.middlewares.test-jwtAuth.plugin.jwtAuth.tokenKey=access_token"
http:
  middlewares:
    test-jwtAuth:
      plugin:
        jwtAuth:
          tokenKey: access_token
[http.middlewares]
  [http.middlewares.test-jwtAuth.plugin.jwtAuth]
    tokenKey = "access_token"

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"