JWTs and Signing Keys

Wristband uses JSON Web Tokens (JWTs) to authenticate requests.

JSON Web Tokens (JWTs) provide a secure, lightweight method for transmitting information between parties. Upon a successful user or machine login, Wristband issues a token that validates their identity across subsequent requests.

JWTs consist of three parts separated by dots: <header>.<payload>.<signature>

  • Header: Specifies the token type and the cryptographic algorithm used to secure it.
  • Payload: Contains the claims, which are statements about the authenticated user or machine.
  • Signature: Verifies that the sender is who they say they are and ensures the message wasn't altered.

JWT Header

The header is the first part of a JWT and is a base64Url-encoded JSON object. It consists of three parts that define how the token is structured and secured:

  • kid (Key ID): Identifies the specific key used to sign the token. This is essential for working with JSON Web Key Sets (JWKS) for key management and rotation, allowing systems to easily look up the correct public key for signature verification.

  • alg (Algorithm): Specifies the cryptographic algorithm used to sign the token, ensuring the recipient knows how to validate the signature. Wristband supports the RS256 algorithm (RSA with SHA-256) for access tokens and ID tokens, and the HS256 algorithm for refresh tokens.

  • typ (Type): Defines the explicit type of the token. This allows the recipient to clearly identify the token's purpose and expected usage, ensuring it is treated appropriately and mitigating token-confusion attacks. Wristband supports the following explicit token types:

    "typ" ValueToken Type
    at+JWTAccess Token
    id+JWTID Token
    rt+JWTRefresh Token

The following is an example of a JWT header for a Wristband access token:

{
  "kid": "r44ow3paz5crpm5dgvuaerhs2a",
  "typ": "at+JWT",
  "alg": "RS256"
}

JWT Payload

The payload is the second part of a JWT and is also a base64Url-encoded JSON object. It houses the claims, which are key-value statements about the authenticated entity—such as the identity of the subject, their granted scopes, and other contextual metadata. Because the payload content is dynamic, the exact claims included will change depending on the token type and the subject being authenticated.

Access Token Payload

Access tokens are short-lived security tokens that grant users and machines permission to access specific resources or perform actions within your application. The specific claims contained in the access token's payload vary depending on the category of the identity being authenticated. Wristband supports the following subject kinds for access tokens:

Subject KindDescription
userA human user
application_clientA machine-to-machine OAuth2 client owned at the application level
tenant_clientA machine-to-machine OAuth2 client owned at the tenant level
User Subject Kind

The following example demonstrates the payload structure of a Wristband access token issued to a human user:

{
  "sub": "dgduqyd42veeffcex6nulzfpba",
  "idp_name": "wristband",
  "van_dom": "auth.yourapp.io",
  "amr": [
    "pwd"
  ],
  "iss": "https://auth.yourapp.io",
  "client_id": "nfqsd5qs4jflzkmhe5ambkieky",
  "tnt_id": "ephrgbr36zgbrplbwi7dkqweui",
  "scope": "openid offline_access",
  "auth_time": 1697517244,
  "is_root_app": true,
  "sub_kind": "user",
  "exp": 1697519044,
  "app_id": "m7k55onh6nbsjggayhubbef4eq",
  "iat": 1697517244,
  "jti": "alltndyuhfcmdbf4ow7zuzs6hq",
  "custom_claims": {
    "claim_a": "foo",
    "claim_b": "bar"
  }
}

The following claims are included in the access token payload for human users who authenticate with Wristband:

ClaimFormatDescription
amrarray of stringsList of authentication method references indicating how the user authenticated. Currently supported methods include:
  • email_link: An email link was used to authenticate the user (e.g. magic link, activation email, etc.)
  • external_idp: An external identity provider was used to authenticate the user.
  • pwd: A password was used to authenticate the user.
app_idstringThe ID of the application associated with the access token. For a user subject, this is the ID of the application that the user belongs to.
auth_timenumberTime at which the user authenticated, represented as the number of seconds from the epoch that have elapsed.
client_idstringThe ID of the OAuth client2 that was used to create the token.
custom_claimsobjectAn object containing configured custom claims.
expnumberThe time at which the access token should expire, represented as the number of seconds from the epoch that have elapsed.
iatnumberThe time at which the access token was issued, represented as the number of seconds from the epoch that have elapsed.
idp_namestringThe name of the identity provider that the user belongs to.
issstringA URL in the format https://<application_vanity_domain>.
jtistringA unique identifier for the token.
scopestringA JSON string containing a space-separated list of OIDC scopes associated with this token. These scopes define what claims will be returned in the userinfo API response.
substringThe ID of the user that was authenticated.
sub_kindstringThe kind of subject the token is for. For a user subject, the value is user.
tnt_idstringThe ID of the tenant associated with the access token. For a user subject, this is the ID of the tenant that the user belongs to.
van_domstringThe vanity domain that was used when calling the token endpoint.
Application Client Subject Kind

The following example demonstrates the payload structure of a Wristband access token issued to an application-level, machine-to-machine (M2M) OAuth2 client:

{
  "sub": "cdgqsdk3mrbfvidlrotm2a4eiu",
  "van_dom": "auth.yourapp.io",
  "iss": "https://auth.yourapp.io",
  "sub_kind": "application_client",
  "exp": 1697674367,
  "app_id": "4satxbaxb5fjncfsqzngnwwswi",
  "iat": 1697587967,
  "jti": "bjgjhrf5jregdjxjcxp4i7ckfe",
  "client_id": "cdgqsdk3mrbfvidlrotm2a4eiu",
  "custom_claims": {
    "claim_a": "foo",
    "claim_b": "bar"
  }
}

The following claims are included in the access token payload for application-level, machine-to-machine (M2M) OAuth2 clients that authenticate with Wristband:

ClaimFormatDescription
app_idstringThe ID of the application associated with the access token. For a client subject that is owned by an application, this will be the application that owns the client.
client_idstringThe ID of the OAuth2 client that was used to create the token.
custom_claimsobjectAn object containing configured custom claims.
expnumberThe time at which the access token should expire, represented as the number of seconds from the epoch that have elapsed.
iatnumberThe time at which the access token was issued, represented as the number of seconds from the epoch that have elapsed.
issstringA URL in the format https://<application_vanity_domain>.
jtistringA unique identifier for the token.
substringThe ID of the client that was authenticated.
sub_kindstringThe kind of subject the token is for. For an application-level OAuth2 client subject, the value is application_client.
van_domstringThe vanity domain that was used when calling the token endpoint.
Tenant Client Subject Kind

The following JSON example demonstrates the payload structure of a Wristband access token issued to a tenant-level, machine-to-machine (M2M) OAuth2 client:

{
  "sub": "h3cdthdxordi3n2jlcobz7mvw4",
  "tnt_id": "rztradojyfc47f74d3fh5fylre",
  "van_dom": "auth.yourapp.io",
  "iss": "https://auth.yourapp.io",
  "sub_kind": "tenant_client",
  "exp": 1697677760,
  "app_id": "4satxbaxb5fjncfsqzngnwwswi",
  "iat": 1697591360,
  "jti": "54eoa4aiybe5zgqdf67jskdyue",
  "client_id": "h3cdthdxordi3n2jlcobz7mvw4",
  "custom_claims": {
    "claim_a": "foo",
    "claim_b": "bar"
  }
}

These are the access token claims for tenant-level machine-to-machine OAuth2 clients that authenticate with Wristband:

ClaimFormatDescription
app_idstringThe ID of the application associated with the access token. For a client subject owned by a tenant, this will be the application that the tenant client belongs to.
client_idstringThe ID of the OAuth2 client that was used to create the token.
custom_claimsobjectAn object containing configured custom claims.
expnumberThe time at which the access token should expire, represented as the number of seconds from the epoch that have elapsed.
iatnumberThe time at which the access token was issued, represented as the number of seconds from the epoch that have elapsed.
issstringA URL in the format https://<application_vanity_domain>.
jtistringA unique identifier for the token.
substringThe ID of the client that was authenticated.
sub_kindstringThe kind of subject the token is for. For a tenant-level OAuth2 client subject, the value is tenant_client.
tnt_idstringThe ID of the tenant that owns the client.
van_domstringThe vanity domain that was used when calling the token endpoint.

ID Token Payload

ID tokens verify a user's identity during authentication and provide applications with essential user profile details.

The following example demonstrates the payload structure of a Wristband ID token issued to an authenticated user:

{
  "at_hash": "IhchIEvr_qd35VVhW2V3Xg",
  "sub": "dgduqyd42veeffcex6nulzfpba",
  "idp_name": "wristband",
  "amr": [
    "pwd"
  ],
  "iss": "https://yourapp-yourcompany.us.wristband.dev",
  "auth_flow": "authorization_code",
  "nonce": "jPy_qLTHFbT1kisduhYgXREVvpY9RPRk97YSywHpk_A",
  "sid": "4glkn7jfqzbdvfycwa6uwhfmry",
  "rt_hash": "xhI6JZLwpvfxltdjtseyHQ",
  "aud": "nfqsd5qs4jflzkmhe5ambkieky",
  "tnt_id": "ephrgbr36zgbrplbwi7dkqweui",
  "auth_time": 1697587898,
  "exp": 1697589698,
  "app_id": "m7k55onh6nbsjggayhubbef4eq",
  "iat": 1697587898,
  "jti": "gzqma35o4nbrnnjrujlpdsxknu",
  "custom_claims": {
    "claim_a": "foo",
    "claim_b": "bar"
  }
}

The following claims are included in the ID token payload for human users who authenticate with Wristband:

ClaimFormatDescription
amrarray of stringsList of authentication method references indicating how the user authenticated. Currently supported methods include:
  • email_link: An email link was used to authenticate the user (e.g. magic link, activation email, etc.)
  • external_idp: An external identity provider was used to authenticate the user.
  • pwd: A password was used to authenticate the user.
app_idstringThe ID of the application that the user belongs to.
at_hashstringThe hash of the access token. An application can calculate the at_hash from its access token and compare it to the one in the ID token to ensure token authenticity and integrity.
audstringThe OIDC spec allows for the aud claim format to be either a string or an array of strings. In Wristband, the aud value will always be a string, and the value should always contain the ID of the OAuth2 client that initiated the authorization request.
auth_flowstringRepresents which authentication flow was used to produce the ID token. Currently supported flows include:
  • authorization_code: The OAuth2 authorization code flow.
  • refresh_token: The OAuth2 refresh token flow.
auth_timenumberTime at which the user authenticated, represented as the number of seconds from the epoch that have elapsed.
custom_claimsobjectAn object containing configured custom claims.
expnumberThe time at which the ID token should expire, represented as the number of seconds from the epoch that have elapsed.
iatnumberThe time at which the ID token was issued, represented as the number of seconds from the epoch that have elapsed.
idp_namestringThe name of the identity provider that the user belongs to.
issstringA URL in the format https://<application_vanity_domain>.
jtistringA unique identifier for the token.
noncestringThe nonce value that was passed in the original authorization request and can be used for verification.
rt_hashstringThe hash of the refresh token. An application can calculate the rt_hash from its refresh token and compare it to the one in the ID token to ensure token authenticity and integrity.
sidstringThe ID of the auth session associated with the ID token.
substringThe ID of the authenticated user.
tnt_idstringThe ID of the tenant that the user belongs to.

Refresh Tokens

Refresh tokens in Wristband are entirely opaque. This means your application should not parse or rely on the internal contents of the token, as it is only meaningful to the Wristband platform internally. The refresh token can't be sent to the Wristband Token Endpoint to retrieve new access tokens for a user.

JWT Signature

The JWT signature is a cryptographic stamp of authenticity applied to the header and payload of a JWT. Wristband Access and ID tokens are signed with the RS256 algorithm.

How it Works

RS256 utilizes an asymmetric key pair:

  • Private key — used for signing
  • Public key — used for verification

Purpose

Recipients of the JWT can verify the signature to confirm its authenticity and also verify that its claims haven't been tampered with.

JSON Web Key Set (JWKS)

A JSON Web Key Set (JWKS) is a standardized format used to represent a collection of public cryptographic keys. As noted previously, Wristband uses an asymmetric key pair to secure tokens—signing them with a private key and publishing a public key for signature verification. Wristband provides a dedicated JWKS Endpoint, allowing your application to dynamically retrieve the public keys needed to verify the integrity of its JWTs.

Signing Key Granularity

Wristband delivers a highly granular scope for isolating cryptographic signing keys. Restricting the operational scope of these keys minimizes the potential blast radius of a security incident, ensuring that a compromised private signing key cannot cause wide-reaching effects across your architecture.

In Wristband, every individual application has its own unique, dedicated signing keys.

Signing Keys Model

Figure 1: Each Wristband application has an independent set of signing keys.

Having per-application signing keys ensures that if an application's keys are compromised, the blast radius is strictly contained.

To illustrate, consider a scenario where Customer X runs two separate applications on the Wristband platform: Application A and Application B. Because each application maintains its own isolated cryptographic boundary, a theoretical breach of the signing keys for Application A has the following outcomes:

Impacted SystemStatus & Impact
Application ATokens issued for this application are compromised and must be rotated immediately.
Application BCompletely unaffected. Its keys, tokens, and users remain entirely secure.
Signing Key Breach Example

Figure 2: A signing-key compromise affects only tokens for the affected application; other applications remain isolated.

To maintain continuous security, Wristband customers can generate new signing keys and rotate out old ones at any time through the Wristband Dashboard or by calling Wristband's APIs directly.

Working with JWTs and Signing Keys

Wristband supports several core operations to help you securely manage your cryptographic keys and active tokens throughout their lifecycle.

Verifying Access and ID Tokens

When verifying the integrity of an access or ID token, your application must validate both the signature and the payload. When inspecting the payload, the two most critical claims to verify are:

  • iss (Issuer): Confirms that the token was actually generated and issued by your specific Wristband application, preventing malicious third-party issuers.
  • exp (Expiration Time): Guarantees that the token is still active and has not expired, preventing malicious actors from replaying old tokens.

Issuer Verification (iss):

Your application should perform a strict string comparison to ensure the iss value exactly matches your expected Wristband domain. Depending on your configuration, this value will be one of the following:

  • Default Application Vanity Domain: The default application vanity domain generated by the platform if custom domains are inactive (e.g., https://yourapp-yourcompany.us.wristband.dev).
  • Custom Domain: A fully branded domain of your choosing if custom domains are active for your application (e.g., https://auth.yourapp.io).

Expiration Time Verification (exp):

Your application should not accept tokens that have an exp timestamp that is in the past. Enforcing strict expiration checks reduces the window of opportunity for an attacker to use a leaked or intercepted token.

Verifying JWT Signatures

Verifying the JWT signature is a critical security step. It authenticates the token's origin, prevents tampering, and protects your application against unauthorized access and token-substitution attacks.

There are two ways you can validate a JWT:

1. Verifying with the Introspection API

Wristband exposes a dedicated Introspect Token API that your application can call to validate tokens in real time. By passing the inbound token to the Introspection API, your backend receives an immediate response confirming whether the token is currently active and valid. If the token is valid, Wristband returns its associated metadata and claims along with the status payload.

📘

Note:

The Introspection API will not only validate the signature of a token, but it also validates the claims in the payload.

While utilizing the Introspection API offloads token validation logic to Wristband, it introduces a performance trade-off. Because your application must hit Wristband's servers to verify each token, this method significantly increases network traffic and introduces added latency to your API requests.

For high-throughput applications, this frequent network overhead can become a performance bottleneck. In this case, we recommend verifying the the token locally using your application's published signing keys.

2. Verifying with Signing Keys

Alternatively, you can utilize the public cryptographic keys available through Wristband’s JWKS API. This is the preferred and recommended method for verifying token signatures because it eliminates runtime network latency.

Your application calls the JWKS endpoint once during startup and caches the public keys in memory. When an API request arrives, your backend pulls the matching key from its local cache to verify the signature instantly, avoiding a network round-trip to Wristband.

Obtaining New Access Tokens

During token verification, if the signature is valid but the access token has expired, your application must obtain a new one. Wristband provides two standard workflows for acquiring a fresh access token based on the identity type:

  • For Human Users: If the user authenticated via the OAuth 2.0 authorization code flow, your application can use their stored refresh token to request a new access token. This is done by calling Wristband's Token API using the refresh_token grant type.
  • For Machine-to-Machine Clients: M2M clients can simply use their client credentials (client ID and client secret) to request a new access token at any time by calling Wristband's Token API using the client_credentials grant type.

Signing Key Rotation

Signing key rotation is a critical security practice that involves the periodic replacement of the cryptographic keys used to sign and verify tokens. Routinely updating these keys reduces the risk of long-term credential exposure and ensures that even if a private key is compromised, its window of usefulness is strictly limited. Within the Wristband platform, each application maintains its own unique set of signing keys that can be rotated independently at any time.

To facilitate seamless key rotation without disrupting active user sessions or causing application downtime, an application's signing keys can exist in one of three distinct lifecycle states (positions):

PositionDescription
CurrentThis is the signing key that is actively being used to sign new tokens.
PreviousAfter rotation, the signing key in the current position will be transitioned to the previous position. In this position, it will no longer be used to sign new tokens but can still be used to verify older tokens that were signed with this key.
NextAfter rotation, the signing key in the next position will be transitioned to the current position. The signing key in the next position can be used to pre-load your application with the signing key that will be used after performing a rotation.

Did this page help you?