A JWT (JSON Web Token) is a type of security token that is used to securely transmit information between parties. It is a compact and self-contained way of representing claims to be transferred between two parties. Many API providers require you to generate a cryptographically signed JWT assertion that includes the authorization you are requesting from the server. If the server accepts the assertions you are claiming in the JWT, it will respond with an access token granting access to the API.

The following diagram shows the overall flow between a client and server using JWT assertions.

JWT Bearer assertion flow

First, the client creates the JSON representation of the access they are requesting from the server, called the JWT assertion, following the https://jwt.io/ standard. Next, the client signs the JWT using their private key. To authorize with the server, the client sends an authorization request including the JWT assertion to the server’s OAuth token granting endpoint. The server validates that the assertion was signed by the client, and that the claims that are present in the JWT match what the server expects from the client. If both the assertion is signed correctly and the claims within the assertion are correct, the server responds with an OAuth response including the access_token field that is used in subsequent requests to the server. Once the client receives this response, it includes the access token in subsequent requests under the Authorization header.

Now that we have an understanding of the overall flow, we can dive in to each of the steps in more detail.

Creating a Signing Key

To use JWT assertions we need a cryptographic key to sign requests with. The details of getting this key and configuring it with your server are usually implementation dependent, but the basic idea remains the same: the client possesses a private key/public key pair, and it shares the public key with the server it wishes to authenticate with. When a JWT bearer assertion request is received by the server, it uses the client’s public key to decrypt the assertion to verify that it was signed by the client’s key.

Often, the key pair for using a particular server will be generated by the server you are authenticating against. Other times, you will need to follow the instructions in your server or APIs’s documentation to generate a key that will work with their system.

If you have an existing key pair, you can skip ahead in this article. If not, can use openssl to generate a self-signing key we can use for the rest of this article.

First, we use the openssl genrsa command to generate an RSA private key:

openssl genrsa -des3 -passout pass:SomePassword -out server.pass.key 2048

Generating RSA private key, 2048 bit long modulus
.............................+++++
.................+++++
e is 65537 (0x10001)

The output file server.pass.key is an RSA private key, which is a simple text file. We can process our key using the openssl rsa command to transform it into a simplified representation output to server.key.

openssl rsa -passin pass:SomePassword -in server.pass.key -out server.key

writing RSA key

Next, we self-sign our key sing a certificate we generate ourselves (server.csr). The openssl req command will ask you to enter some details about the certificate authority that will be incorporated into the certificate request. Since this is self-signed request for testing purposes you can enter any details that make sense for your use case. The openssl x509 will generate a PEM and self-sign with the certificate request we generated. Lastly, the openssl pcks8 command transforms our PEM file into pkcs8 format, which is often what is required by APIs:

openssl req -new -key server.key -out server.csr
openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt
openssl pkcs8 -topk8 -inform PEM -outform DER -in server.key -out private_key.pkcs8 -nocrypt

At the end of this process, you will private key in pcks8 format ready for use in the rest of this article.

Creating the JWT assertion

Now that we have a private key, we can begin the process of generating a signed JWT assertion that will be verified by the server. The JWT follows the format given by RFC 7519.

The JWT assertion is made of three parts: the header, the payload, and the signature. These three parts are separated by dots (.). Therefore, a JWT typically looks like the following.

xxxxx.yyyyy.zzzzz

Creating the Header

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

For example, the following header declares the token is of type JWT and uses the RSA signing algorithm.

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

Then, this JSON is base64 URL encoded form the first part of the JWT. For example, the following bash script declares a JWT header, and uses the openssl base64 command to encode it in URL-safe base64.

HEADER="{\"alg\":\"RS256\"}"
(printf "%s" "$HEADER" | openssl base64 -A | tr '+/' '-_' | tr -d '=')
eyJhbGciOiJSUzI1NiJ9

The output string eyJhbGciOiJSUzI1NiJ9 serves as the header for our JWT.

Creating the Payload

The JWT payload is the second part of the token. This portion contains the claims. Claims are statements about an entity (typically, the user) along with any additional metadata the server implementation requires.

The JWT standard includes a set of “registered” claims which are not mandatory but are recommended to provide interoperability between implementations.

PARAMETERDESCRIPTION
issThe issuer must contain the OAuth client_id or name of the application that you used to register your certificate with the server.
audThe audience is server you are authenticating with. This might be https://login.salesforce.com for example.
subThe principal or subject you are authenticating as. This could be a user, or it could be the client_id you used as the issuer for a server-to-server authorization.
expThe date and time at which the token expires, expressed as the number of seconds from 1970-01-01T0:0:0Z measured in UTC.

For example, here is a full set of JWT claims for an example Salesforce API.

{
    "iss": "my-client-id",
    "sub": "my@email.com",
    "aud": "https://login.salesforce.com",
    "exp": "1333685628"
}

Like the header, this payload is base64 URL encoded to form the second part of the JWT.

ISSUER=my-client-id
SUBJECT=my@email.com
AUDIENCE=https://login.salesforce.com
EXP=1333685628

PAYLOAD="{\"iss\": \"${ISSUER}\", \"sub\": \"${SUBJECT}\", \"aud\": \"${AUDIENCE}\", \"exp\": ${EXPIRY}\"}"
(printf "%s" "$PAYLOAD" | openssl base64 -A | tr '+/' '-_' | tr -d '=')
eyJpc3MiOiAibXktY2xpZW50LWlkIiwgInN1YiI6ICJteUBlbWFpbC5jb20iLCAiYXVkIjogImh0dHBzOi8vbG9naW4uc2FsZXNmb3JjZS5jb20iLCAiZXhwIjogIn0

The long string serves as the payload for our JWT.

Creating the Signature

The last part of the JWT is the signature. To create the signature part you have to take the encoded header and the encoded payload, and sign the two with your private key using the algorithm specified in the JWT header.

For example, if we combine output of the two previous calls into a string called TOKEN, we can sign that token using the private key we previously generated. Concatenating those results with a . in between gives us our full JWT.

TOKEN="$(printf "%s" "$HEADER" | openssl base64 -A | tr '+/' '-_' | tr -d '=').$(printf "%s" "$PAYLOAD" | openssl base64 -A | tr '+/' '-_' | tr -d '=')"

SIGN="$(printf "%s" "$TOKEN" | openssl dgst -sha256 -sign "${KEY}" -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')"

echo "${TOKEN}.${SIGN}"

Putting it all Together

The following bash script puts everything together with two small additions. First, we include a jti in the claim. The jti is a unique identifier for the JWT and acts as a cryptographic nonce. Not all servers require a jti, but it is included in this example. In our script, we generate a UUID to use as the jti. Second, we set the exp, or expiry of the JWT, to 3600 seconds past the current time as a convenience.

#!/bin/bash
if [[ -z "$ISSUER" || -z "$SUBJECT" || -z "$AUDIENCE" || ! -f "$KEY" ]]; then
  echo "Usage: AUDIENCE=your_audience ISSUER=your_issuer SUBJECT=your_subject KEY=/path/to/your_private.key $0"
  exit 1
fi

uuid=
function generateUuid()
{
    local N B T X
    for (( N=0; N < 16; ++N )); do
        B=$(( $RANDOM%255 ))
        if (( N == 6 )); then
            printf -v X '4%x' $(( B%15 ))
            uuid+=$X
        elif (( N == 8 )); then
            local C='89ab'
            printf -v X '%c%x' ${C:$(( $RANDOM%${#C} )):1} $(( B%15 ))g
            uuid+=$X
        else
            printf -v X '%02x' $B
            uuid+=$X
        fi
        for T in 3 5 7 9; do
            if (( T == N )); then
                uuid+='-'
                break
            fi
        done
    done
}

generateUuid

EXPIRY="$(date '+%s')"
EXPIRY=$((EXPIRY+3600))
JTI="$uuid"
HEADER="{\"alg\":\"RS256\"}"
PAYLOAD="{\"iss\": \"${ISSUER}\", \"sub\": \"${SUBJECT}\", \"aud\": \"${AUDIENCE}\", \"exp\": ${EXPIRY}, \"jti\": \"${JTI}\"}"

TOKEN="$(printf "%s" "$HEADER" | openssl base64 -A | tr '+/' '-_' | tr -d '=').$(printf "%s" "$PAYLOAD" | openssl base64 -A | tr '+/' '-_' | tr -d '=')"

SIGN="$(printf "%s" "$TOKEN" | openssl dgst -sha256 -sign "${KEY}" -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')"

echo "${TOKEN}.${SIGN}"

Example Usage

AUDIENCE=your_audience \
ISSUER=your_issuer \
SUBJECT=your_subject \
KEY=/path/to/your_private.key \
./generate-token.sh