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" Value Token Type at+JWT Access Token id+JWT ID Token rt+JWT Refresh 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 Kind | Description |
|---|---|
| user | A human user |
| application_client | A machine-to-machine OAuth2 client owned at the application level |
| tenant_client | A 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:
| Claim | Format | Description |
|---|---|---|
| amr | array of strings | List of authentication method references indicating how the user authenticated. Currently supported methods include:
|
| app_id | string | The 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_time | number | Time at which the user authenticated, represented as the number of seconds from the epoch that have elapsed. |
| client_id | string | The ID of the OAuth client2 that was used to create the token. |
| custom_claims | object | An object containing configured custom claims. |
| exp | number | The time at which the access token should expire, represented as the number of seconds from the epoch that have elapsed. |
| iat | number | The time at which the access token was issued, represented as the number of seconds from the epoch that have elapsed. |
| idp_name | string | The name of the identity provider that the user belongs to. |
| iss | string | A URL in the format https://<application_vanity_domain>. |
| jti | string | A unique identifier for the token. |
| scope | string | A 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. |
| sub | string | The ID of the user that was authenticated. |
| sub_kind | string | The kind of subject the token is for. For a user subject, the value is user. |
| tnt_id | string | The 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_dom | string | The 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:
| Claim | Format | Description |
|---|---|---|
| app_id | string | The 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_id | string | The ID of the OAuth2 client that was used to create the token. |
| custom_claims | object | An object containing configured custom claims. |
| exp | number | The time at which the access token should expire, represented as the number of seconds from the epoch that have elapsed. |
| iat | number | The time at which the access token was issued, represented as the number of seconds from the epoch that have elapsed. |
| iss | string | A URL in the format https://<application_vanity_domain>. |
| jti | string | A unique identifier for the token. |
| sub | string | The ID of the client that was authenticated. |
| sub_kind | string | The kind of subject the token is for. For an application-level OAuth2 client subject, the value is application_client. |
| van_dom | string | The 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:
| Claim | Format | Description |
|---|---|---|
| app_id | string | The 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_id | string | The ID of the OAuth2 client that was used to create the token. |
| custom_claims | object | An object containing configured custom claims. |
| exp | number | The time at which the access token should expire, represented as the number of seconds from the epoch that have elapsed. |
| iat | number | The time at which the access token was issued, represented as the number of seconds from the epoch that have elapsed. |
| iss | string | A URL in the format https://<application_vanity_domain>. |
| jti | string | A unique identifier for the token. |
| sub | string | The ID of the client that was authenticated. |
| sub_kind | string | The kind of subject the token is for. For a tenant-level OAuth2 client subject, the value is tenant_client. |
| tnt_id | string | The ID of the tenant that owns the client. |
| van_dom | string | The 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:
| Claim | Format | Description |
|---|---|---|
| amr | array of strings | List of authentication method references indicating how the user authenticated. Currently supported methods include:
|
| app_id | string | The ID of the application that the user belongs to. |
| at_hash | string | The 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. |
| aud | string | The 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_flow | string | Represents which authentication flow was used to produce the ID token. Currently supported flows include:
|
| auth_time | number | Time at which the user authenticated, represented as the number of seconds from the epoch that have elapsed. |
| custom_claims | object | An object containing configured custom claims. |
| exp | number | The time at which the ID token should expire, represented as the number of seconds from the epoch that have elapsed. |
| iat | number | The time at which the ID token was issued, represented as the number of seconds from the epoch that have elapsed. |
| idp_name | string | The name of the identity provider that the user belongs to. |
| iss | string | A URL in the format https://<application_vanity_domain>. |
| jti | string | A unique identifier for the token. |
| nonce | string | The nonce value that was passed in the original authorization request and can be used for verification. |
| rt_hash | string | The 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. |
| sid | string | The ID of the auth session associated with the ID token. |
| sub | string | The ID of the authenticated user. |
| tnt_id | string | The 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.

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 System | Status & Impact |
|---|---|
| Application A | Tokens issued for this application are compromised and must be rotated immediately. |
| Application B | Completely unaffected. Its keys, tokens, and users remain entirely secure. |

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):
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):
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 codeflow, your application can use their stored refresh token to request a new access token. This is done by calling Wristband's Token API using therefresh_tokengrant 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_credentialsgrant 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):
| Position | Description |
|---|---|
| Current | This is the signing key that is actively being used to sign new tokens. |
| Previous | After 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. |
| Next | After 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. |
Updated 2 days ago