You are here

JSON Web Token (JWT) Authentication

Affects product: 
Airlock WAF
Affects version(s): 
Airlock WAF 7.2 and newer

Create a token

The JWT consists of the header, the payload and the details about the signature. Each part is base64url encoded and separated by a dot.

    Header

The header of the JWT usually consists of 2 parts.

  • The token type, which is JWT.
  • The signing algorithm.

{
    "alg": "HS256",
    "typ": "JWT"
}

    Payload

The payload part of the token contains the claims.
Claims are information about the entity or other additional data.
There are different claim types:

Registered:
These claims are predefined. They are recommended to use but not mandatory.
The registered claims are documented in the RFC 7519 under section 4.1 Registered Claim Names.

Public:
These claims can be defined by anyone using JWT. They can be defined in the IANA JSON Web Token Registry.

Private:
These are custom claims. They can be used to share information between parties which agreed on using them and are neither registered or public claims.

{
    "iss": "Dreamcompany",
    "aud": "Authenticator",
    "exp": 1571670621,
    "nbf": 1571667021,
    "sub": "muster",
    "client_id": "abcd1234",
    "admin": "true"
}

    Signature

The signature part depends on the selected algorithm.
The algorithms are separated in symmetric and asymmetric JWT signatures.

Symmetric JWT signatures

  • HS (HAMC with SHA-2)

HS256
HS384
HS512

If we use symmetric JWT signatures we only need an additional shared secret.
When we use symmetric JWT signatures a HMAC is calculated with the header, payload and shared secret.
Both parties know the shared secret and can calculate the HMAC.
If they are identical the JWT signature is valid.

With the following parts we can build our JWT.

Header:
 

{
    "alg": "HS256",
    "typ": "JWT"
}

Payload:
 

{
    "iss": "Dreamcompany",
    "aud": "Authenticator",
    "exp": 1571670621,
    "nbf": 1571667021,
    "sub": "muster",
    "client_id": "abcd1234",
    "admin": "true"
}

Secret:
 

Secret1234

Python code example:

import time
import jwt
nbfdate = (int(time.time()))
expdate = nbfdate + 3600
payload = {
    "iss": "Dreamcompany",
    "aud": "Authenticator",
    "exp": expdate,
    "nbf": nbfdate,
    "sub": "muster",
    "client_id": "abcd1234",
    "admin": "true"
}
Secret = 'Secret1234'
algorithm = "HS256"
GeneratedJWT = jwt.encode(payload, Secret, algorithm=algorithm)
print(GeneratedJWT)

 

The output is:

b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJEcmVhbWNvbXBhbnkiLCJhdWQi
OiJBdXRoZW50aWNhdG9yIiwiZXhwIjoxNTcxNjcxMjA0LCJuYmYiOjE1NzE2Njc2MDQsInN1YiI
6Im11c3RlciIsImNsaWVudF9pZCI6ImFiY2QxMjM0IiwiYWRtaW4iOiJ0cnVlIn0.T2Gke6ZvIR
kt_OpG0jvB1RxqOjMYKtwLFYyYiKSQ69c'

To use the generated JWT the b'...' must be removed. The b' indicates that it is a bytes type, but this is not a part of the JWT and is only needed in python.

If we put the output into its original blocks it looks like this:

Header:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9​​​​

Payload:

eyJpc3MiOiJEcmVhbWNvbXBhbnkiLCJhdWQiOiJBdXRoZW50aWNhdG9yIi
wiZXhwIjoxNTcxNjcxMjA0LCJuYmYiOjE1NzE2Njc2MDQsInN1YiI6Im11
c3RlciIsImNsaWVudF9pZCI6ImFiY2QxMjM0IiwiYWRtaW4iOiJ0cnVlIn0

Signature:

T2Gke6ZvIRkt_OpG0jvB1RxqOjMYKtwLFYyYiKSQ69c

With the symmetric JWT signature it is easy to create and verify a token.
However, symmetrical signatures prevent the JWT from being shared with another service.
To verify the integrity of the JWT, all services must have access to the same secret key.
But the possession of the secret key is enough to generate JWTs with a valid signature.
Sharing the HMAC secret with a third-party service creates a significant vulnerability.

Instead of sharing the secret HMAC key, you can decide to use asymmetric signatures.

Asymmetric JWT signatures

  • RS (RSA with SHA-2)

RS256
RS384
RS512

  • PS (RSA PSS signature with SHA-2)

PS256
PS384
PS512

  • ES (EC DSA signature with SHA-2)

ES256
ES384
ES512

When we use asymmetric JWT signatures a public/private key pair is used.
A signature signed with the private key can be verified wih the public key.
Only the private key must be kept secret, the public key can be used for all services.

The following commands can be used to create an RSA key pair:

openssl genrsa -out DreamCompanyJWT.pem 2048

openssl rsa -in DreamCompanyJWT.pem -outform PEM -pubout -out publicDreamCompanyJWT.pem

The first command gives us the private key we use to sign the JWT.
The private key should look something like this:

-----BEGIN RSA PRIVATE KEY-----
MIIEoQIBAAKCAQEA0jNLoe6Hmy9l1FaKFWoJJKYacQdvm0D7dgRYVJpcqwXOTLe3
hMTlaOZiOYMznLvxtaZSn5NAV1WTbMusrRvlteYxIEWdwDHOZkeYT1PNxV5AkQ/0
b8UkMuP/iSR5xy6qMDnj0cTKIzdZ9z92/v0gqe56/8fz48THiVblzagfz4V3m10n
GndDbr2nDlhxmC1XFBL3GKUCXm+YXHmKvBsAF+klJHOhM8bgTczAGGvAGzlf/ANW
4ATJdmnyvCceGO9E7S3aNacYYDjvpM3BUKpEy3GRin8jr/k9n/1cIaRPXQSfHzew
xO0/Z6TorkxbNbDjScshA2w3a4QTsNsqr1TREQIDAQABAoIBAQCkg8s1MPhniAA9
4YFvYy9GxM47GqPKMmDdLGdpx+qxW/jr6Ho06tnWcUH9W2egyyRRlJxkEp6v++VH
qF08m3H9HO3kIThnPVQajvxxUdjiGXOVVZRtijFbt0C/uJGrSCWhWx2oh0nhqCe8
sKpUxg6zkVgpKvkCknfNUPrJLEXBZ3xAormmCGYmF9KSck8CTdjalJ+RldOeWmaI
haOcyYomlKDc6Bh0yR/IhhiyRuxL07rd0e5WXRj2SYf2OrLpLUsqikTtIUdkp3D0
v6xv9BpiGMJK4BvYBtDg6JFNsZEUQ8f9alTA9tUXeN7TM9Aw2pxWMkgPlmrPnPXr
E1RmdlyxAoGBAO7jgS9fenh2vKqMOGrdQFwBmCZKP4zqeVaPsO0PVzYVotLjJlGS
jqPiPfb8iWuv98/KwdpDODgWENwNW9Tv0hi6N/X2M2XyJJCplgJsex91BFsPZZgI
XoUQkTVLJUTlhE9dDsTPSw0WLpnP9JWhQiBNea3mkjRTXbgNCWV0UsLFAoGBAOFB
u+Jx/qxr+nR0PvPtIA/qzKf/0xaxtvUjuthi1pw3ag3fIrEQOaK4mTIRADKYvkxX
yCbAgc6abM/AuSSeGv1qNT6DDFJ4Ohq7/U0Hr92IM1N1xb/Hg1vdcENf7qjGbG5v
zYbhsmWX8wUjGEPjFfZF/wm+p8LpKAW0sUl8fsndAoGABORHuu6blWfpCR0B8r5B
GSE3OmF+16XdgjZDCzerM4JR6TuZh0YXAagaWLpnHftmeazPN2b0+cEJ61eir8OR
a8w0ZSb7enTw5r1yQu5xvv0ob6zrCafyN4tVTeASDU0d3norWUBdaxEvNxsZCQ9a
SUsM2NDF4ahrXN93d8pf/3kCgYA8zHpC55PexBJIxmuLeTBTO4PbwIAgOv1v8yHm
b98PMSHN9rEta3yy//JRgic0gOe0QWlDlGg09Jd0B8exK3+LcNRIOCTmSY8dUipP
VUjRmYaI+1vRpK8+52j6tnT7VvtDX6fwxRBavYWZmFxfTnbKDyCEZ9JuGHJ1/8xu
Uk0CfQJ/H8o4N9TzQVFgtoxQ/kYv0aPtZMycSzPGco8rH/lIODhBDF/55SPEoYJj
K74Yg2dnSVS/fhqAwKa7flwMhJb0P9k4ApQ9wrgkvPCWavM3FfJanpoNmc8Qvytv
Tit2llxVj1S1QBqSf9xSBA98v4aFHEKp2JmZHzZ9RJmt3lzOZg==
-----END RSA PRIVATE KEY-----

The second command generates the public key from the private key.
The public key should look something like this:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0jNLoe6Hmy9l1FaKFWoJ
JKYacQdvm0D7dgRYVJpcqwXOTLe3hMTlaOZiOYMznLvxtaZSn5NAV1WTbMusrRvl
teYxIEWdwDHOZkeYT1PNxV5AkQ/0b8UkMuP/iSR5xy6qMDnj0cTKIzdZ9z92/v0g
qe56/8fz48THiVblzagfz4V3m10nGndDbr2nDlhxmC1XFBL3GKUCXm+YXHmKvBsA
F+klJHOhM8bgTczAGGvAGzlf/ANW4ATJdmnyvCceGO9E7S3aNacYYDjvpM3BUKpE
y3GRin8jr/k9n/1cIaRPXQSfHzewxO0/Z6TorkxbNbDjScshA2w3a4QTsNsqr1TR
EQIDAQAB
-----END PUBLIC KEY-----

With all the following parts we can build our JWT.

Header:

{
    "alg": "RS256",
    "typ": "JWT"
}

Payload:

{
    "iss": "Dreamcompany",
    "aud": "Authenticator",
    "exp": 1571670621,
    "nbf": 1571667021,
    "sub": "muster",
    "client_id": "abcd1234",
    "admin": "true"
}

Private key:

-----BEGIN RSA PRIVATE KEY-----
MIIEoQIBAAKCAQEA0jNLoe6Hmy9l1FaKFWoJJKYacQdvm0D7dgRYVJpcqwXOTLe3
...
-----END RSA PRIVATE KEY-----


Python code example:

import jwt
payload = {
    "iss": "Dreamcompany",
    "aud": "Authenticator",
    "exp": 1571670621,
    "nbf": 1571667021,
    "sub": "muster",
    "client_id": "abcd1234",
    "admin": "true"
}
PRIVATE_RSA_KEY= b'''-----BEGIN RSA PRIVATE KEY-----
MIIEoQIBAAKCAQEA0jNLoe6Hmy9l1FaKFWoJJKYacQdvm0D7dgRYVJpcqwXOTLe3
...
-----END RSA PRIVATE KEY-----'''
GeneratedJWT = jwt.encode(payload, PRIVATE_RSA_KEY, algorithm='RS256')
print(GeneratedJWT)

The output is:

b'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJEcmVhbWNvbXBhbnkiLCJhdWQi
OiJBdXRoZW50aWNhdG9yIiwiZXhwIjoxNTcxNjcwNjIxLCJuYmYiOjE1NzE2NjcwMjEsInN1YiI
6Im11c3RlciIsImNsaWVudF9pZCI6ImFiY2QxMjM0IiwiYWRtaW4iOiJ0cnVlIn0.s4C3p9kVNF
eRAB5tChatC3ldQX07v9mG7thL7FeEO6cClfNuiaLSgq8f8ymbfO3OQYW_KuwaA1KYRuoy1JmKk
4DBbYLcz6aoABe0pzI5Z_6wgMzAyqz8pQtwDAcd4Idoi8JdRbtzZce9o-0nZiFA4hVAXqYwpEYC
4UU8ZmJO_z8tY4juHPTV3nDugdtqyNnmAiBoLryOfGNngQZccdY1_QvkXS1y0bg1a0k8cVVtnq-
_93fYJIt9Z64CTvlH3uOeh7uaEv3nIxpXhvhkTySpUmY8e04TO09oTyZijiloByv3KFQ92OOJ8L
5N5_CeEc5p9LWjT1pcX8ATamOycZz2Q'

To use the generated JWT the b'...' must be removed. The b' indicates that it is a bytes type, but this is not a part of the JWT and is only needed in python.

If we put the output into its original blocks it looks like this:

Header:

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9

Payload:

eyJpc3MiOiJEcmVhbWNvbXBhbnkiLCJhdWQiOiJBdXRoZW50aWNhdG9yIi
wiZXhwIjoxNTcxNjcwNjIxLCJuYmYiOjE1NzE2NjcwMjEsInN1YiI6Im11
c3RlciIsImNsaWVudF9pZCI6ImFiY2QxMjM0IiwiYWRtaW4iOiJ0cnVlIn0

Signature:

s4C3p9kVNFeRAB5tChatC3ldQX07v9mG7thL7FeEO6cClfNuiaLSgq8f8ymbfO3OQYW_KuwaA1K
YRuoy1JmKk4DBbYLcz6aoABe0pzI5Z_6wgMzAyqz8pQtwDAcd4Idoi8JdRbtzZce9o-0nZiFA4h
VAXqYwpEYC4UU8ZmJO_z8tY4juHPTV3nDugdtqyNnmAiBoLryOfGNngQZccdY1_QvkXS1y0bg1a
0k8cVVtnq-_93fYJIt9Z64CTvlH3uOeh7uaEv3nIxpXhvhkTySpUmY8e04TO09oTyZijiloByv3
KFQ92OOJ8L5N5_CeEc5p9LWjT1pcX8ATamOycZz2Q

The token can also be generated online (JWT.io) or in your own program.
The token also can be verified on JWT.io.

Further JWT validation

Using JWT securely goes further than just verifying the signature.
Beside from the signature, the JWT should contain a few other security-related properties.
These properties come in the form of reserved claims that can be included in the payload of the Token.

exp (Expiration):
The issuer uses this claim to indicate the expiration date of the JWT.
If this expiration date is in the past, the JWT has expired and must not be rejected.

nbf (Not before):
It indicates the point in time when the JWT becomes valid.

A JWT can only be accepted if this timestamp is in the past.

iss (Issuer):
This claim indicates the identity of the party who created the JWT.
The consumer should always check that the "iss" matches the expected issuer.

aud (Audience):
This claim indicates for whom the JWT was issued.
The consumer should always verify the "aud" claim.

WAF configuration

To implement the JWT authentication on the Airlock WAF you need at least WAF_Version 7.2.

The configuration must be done at the access tab in the mapping.
Switch "Access Tokens" from OFF to ON.
Select "Extract header". The option "extract header" allows you to extract an access token from a specific header. A specific patter and its rewrite can be specified.

Select Token mandatory.
If enabled, all requests without a token are denied.

Configure the Token type, the signing method and enter the public key.

When the checkbox "Check expiry / not before" is enabled the claims "exp" and "nbf" are validated.

Claim restrictions
With the claim restrictions we can check for any claim.
Either whether it exists with any value or with a specific value.
For example, this can be used to validate the "iss" or "aud" claims.

Claim name: "iss"
Claim restriction regex: "Dreamcompany"
Claim name: "sub"
Claim restriction regex: ".*"

Role extractions
With the role extractions the WAF can assign different roles based on the JWT claims and their values.

Claim name: "admin"
Claim rewrite from: "true"
Role rewrite to: "adminrole"
Use token lifetime as role lifetime: Disabled

With the option "Set audit token from subject" the "sub" claim of the JWT is extracted and its value is used as audit token of the Airlock WAF session.

The Access restrictions and the Authentication flow still have to be configured. Otherwise anyone who has a token can access the back-end, even if the token signature is wrong.

The Access restrictions can be configured as usual, as it would be done without a token.
If the Authentication flow is set to "Redirect",  a request with an invalid token or missing role will result in a redirect to the denied access URL. All other authentication flows will result in a 403 Forbidden HTTP Response.

After activating the new configuration on your WAF the configuration can be tested with a Curl request.

Example curl request:

curl -vk -H 'Accept: application/json' -H "Authorization: Bearer "\
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJEcmVhbWNvbXBhbnkiLCJhdWQi"\
"OiJBdXRoZW50aWNhdG9yIiwiZXhwIjoxNTcxNjcwNjIxLCJuYmYiOjE1NzE2NjcwMjEsInN1Y"\
"iI6Im11c3RlciIsImNsaWVudF9pZCI6ImFiY2QxMjM0IiwiYWRtaW4iOiJ0cnVlIn0.s4C3p9"\
"kVNFeRAB5tChatC3ldQX07v9mG7thL7FeEO6cClfNuiaLSgq8f8ymbfO3OQYW_KuwaA1KYRuo"\
"y1JmKk4D-BbYLcz6aoABe0pzI5Z_6wgMzAyqz8pQtwDAcd4Idoi8JdRbtzZce9o-0nZiFA4hV"\
"AXqYwpEYC4UU8ZmJO_z8tY4juHPTV3nDugdtqyNnmAiBoLryOfGNngQZccdY1_QvkXS1y0bg1"\
"a0k8cVVtnq-_93fYJIt9Z64CTvlH3uOeh7uaEv3nIxpXhvhkTySpUmY8e04TO09oTyZijiloB"\
"yv3KFQ92OOJ8L5N5_CeEc5p9LWjT1pcX8ATamOycZz2Q" https://DreamCompany/TestJWT/

 

Knowledge Base Categories: