JWT

JSON Web Tokens (JWT) are an open, industry-standard method (RFC 7519) for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

Construction of JWT

A JWT is composed of three parts, separated by dots (.), which are -

  • Header

  • Payload

  • Signature

Therefore, a JWT typically looks like the following -

[HEADER].[PAYLOAD].[SIGNATURE]

The header typically consists of two parts: the type of token, which is JWT, and the signing algorithm being used, such as HMAC SHA256 or RSA.

// Example of Header Content

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

This JSON is Base64Url encoded to form the first part of the JWT. It contains the metadata about the token, typically identifying the token type (typ) and the hashing algorithm (alg) used, encoded in JSON format.

Payload

The payload contains the claims. Claims are statements about an entity (typically, the user) and additional data. Claims provide identifying information about the logged-in user. There are no restrictions on the payloads sizes; however, it should be kept short.

There are three types of claims: registered, public, and private claims.

  • Registered Claims: These are a set of predefined claims which are not mandatory but recommended, to provide a set of useful, interoperable claims. Some of them are: iss (issuer), exp (expiration time), sub (subject), aud(audience), and others.

  • Public Claims: These can be defined at will by those using JWTs. But to avoid collisions they should be defined in the IANA JSON Web Token Registry or be defined as a URI that contains a collision resistant namespace. Example: UUID (Universally Unique Identifier), or an Object Identifier (OID)

  • Private Claims: These are the custom claims created to share information between parties that agree on using them and are neither registered or publicclaims. Private claims may subject to collision.

// Example of Payload Content

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

The payload is then Base64Url encoded to form the second part of the JSON Web Token.

Signature

To create the signature part, you have to take the encoded header, the encoded payload, a secret, the algorithm specified in the header, and sign that.

For example if you want to use the HMAC SHA256 algorithm, the signature will be created in the following way:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

The signature is used to verify the message wasn't changed along the way, and, in the case of tokens signed with a private key, it can also verify that the sender of the JWT is who it says it is.

Python Exercise

Write a code in any language for generating the Signature mentioned above.

import base64
import hmac
import hashlib

# Function to encode a string to Base64
def base64_encode(input_string):
    # Convert the string to bytes
    bytes_input = input_string.encode('utf-8')
    # Encode the bytes to Base64
    base64_bytes = base64.b64encode(bytes_input)
    # Convert the Base64 bytes back to a string
    base64_string = base64_bytes.decode('utf-8')
    return base64_string

# Function to create HMAC SHA256 signature
def create_signature(header, payload, secret):
    # Encode the header and payload to Base64
    base64_header = base64_encode(header)
    base64_payload = base64_encode(payload)
    
    # Concatenate the Base64 encoded strings with a period (.)
    message = base64_header + "." + base64_payload
    
    # Convert the secret to bytes
    secret_bytes = secret.encode('utf-8')
    # Convert the message to bytes
    message_bytes = message.encode('utf-8')
    
    # Create a new HMAC object using the secret and SHA256
    hmac_object = hmac.new(secret_bytes, message_bytes, hashlib.sha256)
    # Generate the HMAC signature
    signature = hmac_object.digest()
    # Encode the signature to Base64
    base64_signature = base64.b64encode(signature).decode('utf-8')
    
    return base64_signature

# Main function to create the sign
def main():
    # Define header, payload, and secret
    header = "your_header_string"
    payload = "your_payload_string"
    secret = "your_secret_string"
    
    # Create the HMAC SHA256 signature
    signature = create_signature(header, payload, secret)
    
    # Print the signature
    print("Signature:", signature)

# Call the main function
if __name__ == "__main__":
    main()

The provided Python script generates a cryptographic signature using the HMAC SHA256 algorithm. It consists of two main functions and a main execution block.

Why Convert to Bytes?

  • Base64 Encoding: The Base64 encoding process works with bytes. Therefore, any string that needs to be Base64 encoded must first be converted to bytes.

  • HMAC Signature Creation: The HMAC algorithm works with bytes. It requires the key (secret) and the message to be in bytes to perform the hashing operation. This is because cryptographic algorithms operate at the byte level, processing binary data directly.

Why Use 'utf-8'?:

  • Standard Encoding: 'utf-8' is a widely used character encoding standard that can represent every character in the Unicode character set. It ensures that any text (including special characters and symbols) is correctly converted to bytes and back to text without data loss.

  • Compatibility: Using 'utf-8' ensures compatibility across different systems and platforms. It is the default encoding for web protocols and many programming languages, making it a safe choice for text encoding.

  • Consistency: Encoding and decoding strings using 'utf-8' ensures consistent behavior and avoids potential issues with non-ASCII characters, which might be handled differently in other encodings.

Signing Algorithms

The purpose of the signature section in a JWT (JSON Web Token) is to allow intermediaries to verify the authenticity of the token, ensuring that it has not been tampered with. The process of checking the signature of a JWT is known as validation or token validation. Various signing algorithms are used to sign the header and payload sections. These algorithms are broadly divided into two types:

Symmetric Key Algorithms

In a symmetric-key algorithm, a single secret key is used to encrypt and decrypt the data. One commonly used symmetric key algorithm is HMAC (Hash-based Message Authentication Code).

  • HMAC: HMAC takes a hash function, a message, and a secret key as inputs and produces a hash value as output. The strength of the cryptographic hash function ensures that the message cannot be altered without the secret key. Using a weak hash function may allow malicious users to compromise the validity of the output. Therefore, strong hash functions must be used with HMAC.

  • HMAC + SHA256 (HS256): The SHA2 family of hash functions, including SHA256, is still considered secure by today's standards. The hashes of the header and payload sections are generated using the SHA256 hashing algorithm. These signing algorithms facilitate the easy creation and validation of tokens and should be used when all intermediary parties can be trusted to secure the secret key.

Asymmetric Algorithms

In asymmetric key algorithms, two different keys are used: one for encryption and one for decryption. The private key, which is kept secret, is used to encrypt the data, while the public key, which is publicly available, is used to decrypt the data.

  • RSA + SHA256 (RS256): In this algorithm, the identity provider holds the private key used to generate the signature, while the JWT token consumer uses the public key to verify the signature. RSA algorithms are commonly used in microservice architectures where you cannot trust the opposite party with your private key. Sharing the private key would allow the party to generate arbitrary tokens, posing a severe security threat. Thus, using RSA with a public/private key pair helps maintain security in such environments.

There are several other algorithms available that can be used to create a JWT.

Example of JWT

Lets take the followings as example -

Header

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

Payload

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

Secret

secret

Construction

Step 1: Encode the Header in Base64

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Step 2: Encode the Payload in Base64

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ

Step 3: Create the Signature

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)

Resulting JWT

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

Code Example for Generating JWT

import jwt
import datetime

# Define the secret key
secret_key = 'this-is-my-secret'

# Define the headers
headers = {
    "alg": "HS256",
    "typ": "JWT"
}

# Define the payload
payload = {
    "sub": "1234567890",
    "name": "Navid Naf",
    "iat": int(datetime.datetime.utcnow().timestamp())
}

# Generate the JWT
encoded_jwt = jwt.encode(payload, secret_key, algorithm="HS256", headers=headers)

print(f"Generated JWT: {encoded_jwt}")

Low-level Technical Architecture in Generating JWT

Header Creation: A JSON object specifying the algorithm used for the signature and the type of token is created. This object is then Base64Url encoded.

Payload Creation: A JSON object containing the claims is created. This object is then Base64Url encoded.

Signature Creation:

  • Concatenate the encoded header and payload with a period (.).

  • Apply the cryptographic algorithm specified in the header to the concatenated string using a secret key.

  • The result is then Base64Url encoded to form the final part of the JWT.

Token Assembly: The JWT is constructed by concatenating the encoded header, the encoded payload, and the encoded signature with periods (.) separating them.

By following these steps, a JWT is generated which can then be used to securely transmit information between parties. The receiving party can verify the integrity and authenticity of the token by using the same secret key (in case of symmetric algorithms) or the corresponding public key (in case of asymmetric algorithms).

How Does JWT Work?

In authentication, when a user successfully logs in using their credentials, a JSON Web Token (JWT) is returned. Because tokens function as credentials, they must be managed securely.

Whenever the user wants to access a protected route or resource, the user agent should send the JWT, typically in the Authorization header using the Bearer schema. The content of the header should look like this:

Authorization: Bearer <token>

This can sometimes serve as a stateless authorization mechanism. The server’s protected routes will check for a valid JWT in the Authorization header, granting access if present. If the JWT includes necessary data, database queries may be reduced, although not always.

Sending the token in the Authorization header avoids Cross-Origin Resource Sharing (CORS) issues since it doesn't use cookies.

Let's take a broader look at the following diagram -

The process can be divided into two parts -

Obtaining a JWT

  1. When a user logs in with their credentials, a JSON Web Token (JWT) is issued.

  2. To access a protected route or resource, the user agent sends the JWT in the Authorization header using the Bearer schema.

  3. The server checks for a valid JWT in the Authorization header. If valid, access is granted to the protected resource.

Accessing Resource with JWT

  1. The application/resource server receives a request containing a JWT.

  2. The JWT is Base64-URL decoded.

  3. The server retrieves the algorithm type from the header section of the JWT.

  4. Using the header and payload sections, the server generates its own hash and encrypts it using the secret key.

  5. If the new signature matches the received signature, the server treats the JWT as valid.

  6. Since the payload includes user identifiable information, the server can now authorize access to the required resources.

Types of Tokens

There are multiple set of tokens and each are used for their own different purposes. The most common in JWT based authentications are Access Tokens and Refresh Tokens.

Access Tokens

When a user authenticates, they are given an access token to make authorized calls to the API server. This token includes all the information the server needs to decide whether the user or device can access the requested resource. However, access tokens have a short lifespan, after which the user can no longer make authorized requests to the application server. The issuer party controls the lifespan of an access token. These tokens contain claims such as "iat" (Issued at) and "exp" (Expiration Time), which indicate when the token will expire.

Refresh Tokens

A refresh token is a special type of token used to obtain a new access token. When an access token expires, the user would typically need to re-authenticate. However, this can be avoided by using refresh tokens. As the access token nears its expiry, the refresh token makes an API call to get a new access token from the server. This significantly enhances the authentication process, allowing the user to authenticate only once, with subsequent authentications handled by the refresh token.

References

Last updated