ID Token Verification

Verify Hawcx-issued ID Tokens with any OIDC-compatible JWT library

Hawcx issues OIDC Core 1.0 ID Tokens. After a user authenticates through the Hawcx SDK, your backend redeems the resulting authorization code for a signed ID Token at Hawcx's token endpoint, then verifies the token with any OIDC-compatible JWT library such as python-jose, jose for Node, nimbus-jose-jwt, or java-jwt.

The authentication flow itself is proprietary Hawcx, not OIDC. Only the issued token and the JWKS endpoint use OIDC-shaped pieces. This means your backend uses a JWT library, not a full OIDC client library that auto-discovers and handles browser redirects.

Page Summary

  • Hawcx mints standards-compliant signed JWTs (OIDC Core 1.0 §2 ID Tokens).
  • Verify them with any OIDC-compatible JWT library configured with your issuer URL and audience.
  • Public signing keys are published at a JWKS endpoint.
  • A discovery document is provided as a convenience for libraries that auto-fetch it; fetching the JWKS endpoint directly works equally well.
  • Each Hawcx environment has its own issuer string.

Issuer URLs

The issuer URL is the string your JWT library compares against the token's iss claim. Configure it as a constant in your backend.

EnvironmentIssuer URL
Productionhttps://api.hawcx.com
SandboxProvided in the Admin Console when your sandbox project is created

You need a JWT library, not a full OIDC client

To consume Hawcx identity your backend only verifies the issued ID Token — a standard JWT library plus the JWKS is enough, and a full OIDC client is optional. The authorization-code + PKCE flow (including authorization_endpoint) is advertised in the discovery document and handled by your RP integration; see OIDC Single Sign-On. The document omits only userinfo_endpoint, registration_endpoint, and revocation_endpoint (no UserInfo, dynamic registration, or token revocation).

Setting Up Token Verification

To verify Hawcx-issued tokens, you need:

  1. A Hawcx project provisioned in the Admin Console.
  2. Your project credential (X-Config-Id), issued at project creation and injected by the Hawcx API gateway when your backend calls the token endpoint.
  3. Your project's Client ID (called Client ID in the Admin Console; stamped into every ID Token as the aud claim). Auto-generated as a UUID at project creation; renameable any time, see Changing the audience value. Pin it in your backend as the audience argument to your JWT library.
  4. Your issuer URL, environment-specific and published in the Admin Console.

Once those values are in your backend configuration, point a JWT library at the JWKS endpoint and pass audience= and issuer= to its verify call.

Add client_id to your backend to enable the newer verification features

The newer backend SDK APIs — discovery mode, e.g. HawcxOAuth.fromIssuer({ issuer, configId, clientId }) (Node) and its Python/Java equivalents — require your project's Client ID as the client_id parameter (for example HAWCX_CLIENT_ID). You must add it to your backend to use any of the following:

  • Audience (aud) enforcement — your backend rejects any ID token whose aud isn't your Client ID. This is what stops one app from accepting another app's token when multiple apps share a project (OIDC Single Sign-On).
  • Issuer (iss) enforcement — rejects tokens from an unexpected issuer.
  • Discovery-based configuration (fromIssuer) — endpoints are resolved from /.well-known/openid-configuration, keeping you forward-compatible with new OIDC/SSO discovery fields.
  • private_key_jwt client authentication (RFC 7523) — your client_id becomes the iss/sub of each signed assertion, reusing your OIDC signing key. It applies both to confidential clients at the token endpoint and to management / step-up API calls (each uses a different aud); see private_key_jwt authentication.

The legacy single-arg constructor does not take a client_id: it verifies the signature only and does not check iss or aud, so none of the features above apply. New integrations should set client_id and use discovery mode.

Accessing the Service

The JWKS Endpoint

The JWKS endpoint is the only Hawcx endpoint your JWT library needs to fetch in order to verify tokens.

curl https://api.hawcx.com/.well-known/jwks.json

Returns a standard JSON Web Key Set. Match the kid from the ID Token's JWT header against keys[].kid to select the verification key.

See the JWKS endpoint reference for the response schema.

The Discovery Document (Optional)

Hawcx provides an OIDC discovery document for libraries that auto-fetch it. It is not required; verification works equally well by fetching the JWKS endpoint directly.

curl https://api.hawcx.com/.well-known/openid-configuration
{
  "issuer": "https://api.hawcx.com",
  "authorization_endpoint": "https://api.hawcx.com/authorize",
  "token_endpoint": "https://api.hawcx.com/oauth2/token",
  "jwks_uri": "https://api.hawcx.com/.well-known/jwks.json",
  "response_types_supported": ["code"],
  "response_modes_supported": ["query"],
  "grant_types_supported": ["authorization_code"],
  "code_challenge_methods_supported": ["S256"],
  "subject_types_supported": ["public"],
  "id_token_signing_alg_values_supported": ["ES512"],
  "scopes_supported": ["openid", "profile", "email"],
  "token_endpoint_auth_methods_supported": ["none", "private_key_jwt"],
  "token_endpoint_auth_signing_alg_values_supported": ["EdDSA"],
  "claims_supported": [
    "iss", "sub", "aud", "iat", "nbf", "exp", "jti",
    "auth_time", "nonce", "amr", "acr", "sid",
    "email", "email_verified",
    "phone_number", "phone",
    "cnf"
  ],
  "end_session_endpoint": "https://api.hawcx.com/oauth2/end_session",
  "backchannel_logout_supported": true,
  "backchannel_logout_session_supported": true
}

For token verification your backend reads only jwks_uri, issuer, and id_token_signing_alg_values_supported — a standard JWT library uses these directly, so a full OIDC client is optional. The authorization_endpoint and the rest of the authorization-code + PKCE flow are advertised here too, but are driven by your RP integration, not by token-verification code. The only fields the document omits are userinfo_endpoint, registration_endpoint, and revocation_endpoint.

See the Discovery document reference for the full field list.

Authenticating the User

The end-to-end flow has three stages:

  1. User authentication, performed by the Hawcx SDK using Hawcx's proprietary protocol, not by an OIDC redirect. On success, your backend receives an authorization code from the SDK.
  2. Code exchange, where your backend POSTs the code and PKCE verifier to the Hawcx token endpoint and receives a signed id_token.
  3. Token verification, where your backend verifies the ID Token's signature and claims using the public JWKS.

Stages 2 and 3 are standards-shaped: stage 2 resembles RFC 6749 §4.1.3 (with PKCE per RFC 7636), and stage 3 is plain JWT verification per OIDC Core 1.0 §2 + §3.1.3.7. Stage 1 is Hawcx-specific.

Exchange code for an ID Token

POST /oauth2/token HTTP/1.1
Host: api.hawcx.com
Content-Type: application/x-www-form-urlencoded
X-Config-Id: <your_project_credential>

code=<authorization_code>&code_verifier=<pkce_verifier>

Returns:

{
  "id_token": "eyJraWQiOi...",
  "token_type": "Bearer",
  "expires_in": 60
}

The authorization code is single-use and short-lived. Redeem it immediately on receipt. See the token endpoint reference for full request/response/error semantics.

Validating an ID Token

Always pass audience, issuer, and an algorithm allowlist

Most JWT libraries silently skip the aud and iss equality checks when the expected values aren't provided. Without them, your service would accept any Hawcx-signed token from any project, defeating the protection these claims exist to provide — the single most common integration mistake. Likewise always pass an explicit algorithms allowlist matching what discovery advertises in id_token_signing_alg_values_supported (currently ["ES512"]); never let the token's own header select the algorithm and never accept none, which is what prevents algorithm-confusion attacks.

Every ID Token is a signed JWT. Validation steps (per OIDC Core §3.1.3.7):

  1. Decode the JWT and read the kid from the header.
  2. Fetch (and cache) the JWKS. Select the key whose kid matches.
  3. Verify the signature, pinning the algorithm to an explicit allowlist matching discovery's id_token_signing_alg_values_supported (currently ES512). Never let the token header choose the algorithm and never accept none — this prevents algorithm-confusion attacks.
  4. Verify the iss claim equals your configured issuer URL.
  5. Verify the aud claim equals your project's audience.
  6. Verify the exp claim is in the future and iat / nbf are in the past, allowing a small clock-skew tolerance (e.g. 60 seconds).
  7. Verify nonce equals the value you supplied at auth time, if applicable.

Any compliant JWT library performs steps 1 through 6 automatically when you pass audience=, issuer=, and an explicit algorithms= allowlist.

Example: Python

from jose import jwt
import httpx

ISSUER = "https://api.hawcx.com"
AUDIENCE = "your-project-audience"

# Fetch JWKS directly (no discovery needed)
jwks = httpx.get(f"{ISSUER}/.well-known/jwks.json").json()

claims = jwt.decode(
    id_token,
    jwks,
    algorithms=["ES512"],  # must match discovery's id_token_signing_alg_values_supported
    audience=AUDIENCE,
    issuer=ISSUER,
)

user_id = claims["sub"]

Example: Node.js

import * as jose from 'jose';

const ISSUER = 'https://api.hawcx.com';
const AUDIENCE = 'your-project-audience';

const jwks = jose.createRemoteJWKSet(
  new URL(`${ISSUER}/.well-known/jwks.json`)
);

const { payload } = await jose.jwtVerify(idToken, jwks, {
  issuer: ISSUER,
  audience: AUDIENCE,
  algorithms: ['ES512'], // must match discovery's id_token_signing_alg_values_supported
});

const userId = payload.sub;

Obtaining User Information

Hawcx does not expose a UserInfo endpoint. Every claim about the user is minted directly into the ID Token. After verification, read the claims you need from the verified payload:

FieldClaimNotes
Stable user IDsubThe identity key linking your user records to Hawcx. Most apps store it as a unique-indexed column (e.g. users.hawcx_sub) and keep their own internal primary key.
EmailemailPresent when the user has an email on file.
Email verifiedemail_verifiedtrue whenever email is present; reflects presence, not an independent re-verification at token time.
Phonephone_numberOIDC Core §5.1 standard claim. The legacy phone claim is also emitted during the deprecation window.
Authentication timeauth_timeUnix epoch seconds for when the auth flow completed.
Session IDsidPresent under an SSO session; correlates a login with Back-Channel Logout.
Methods usedamrRFC 8176 array describing the factors. See the table below.

See the ID Token reference for the complete claim list.

Advanced Topics

Caching the JWKS

Hawcx does not currently send Cache-Control headers on the JWKS endpoint. A reasonable default is to cache it for 5 to 10 minutes. On signature verification failure, force-refetch the JWKS before retrying. This handles the case where a key was rotated since your cache was populated.

Key Rotation Posture

Hawcx currently uses a single signing key per environment. If a key rotation occurs, you will be notified in advance, and the JWKS endpoint will publish the new key. Clients must re-fetch JWKS on kid lookup miss.

Hawcx-Specific Claims

In addition to standard OIDC claims, ID Tokens include the following extensions:

ClaimDescription
amrAuthentication Methods References per RFC 8176. Always includes "swk" (proof-of-possession of the device-bound software key). When MFA is performed, "mfa" is added along with the specific factor ("otp" for TOTP or email OTP, "sms" for SMS OTP).
acrAuthentication Context Class Reference. Currently urn:hawcx:pwdless:v1, indicating Hawcx's device-bound, passwordless authentication.
cnfConfirmation claim for token binding. When present, contains jkt, the SHA-256 thumbprint of the JWK the token is bound to. See device-bound authentication.

Interpreting amr

The amr array tells your application which authentication factors were used. Possible shapes:

Token amrMeaning
["swk"]Device-bound authentication only, no MFA.
["swk", "mfa", "otp"]Device auth plus an OTP factor (TOTP or email OTP).
["swk", "mfa", "sms"]Device auth plus an SMS OTP factor.
["swk", "mfa"]Device auth plus a second device factor.

The legacy top-level mfa_method claim is no longer emitted. Read amr instead.

Changing the audience value

The aud claim's value is the project's Client ID in the Admin Console, auto-generated as a UUID v4 at project creation. Renameable any time.

To change it:

  1. Admin Console → your project → Settings → OAuth (or CMDB console Security section).
  2. Edit the Client ID field and save.
  3. Update your backend's audience constant and deploy.

Constraints:

  • ASCII alphanumeric plus ., _, -. Max 128 characters.
  • Globally unique across the environment. Duplicates return 409 CLIENT_ID_CONFLICT.

Rotating the Client ID is a coordinated change

Tokens issued before the rename carry the old aud and stay valid until they expire (default 60s); tokens issued after carry the new aud. If your backend pins a single audience, either accept both values during an overlap window equal to your id_token TTL, or invalidate sessions on rotation and force re-authentication.

Multi-Environment Configuration

If your application runs against multiple Hawcx environments (production and sandbox, for instance), keep the issuer and audience values per-environment. Verifying a sandbox token against the production issuer fails by design: the token's iss claim won't match.

Next Steps