Skip to main content
NoResponse assesses each new account for abuse. It returns the current risk score of the account, from 0 to 100, where a higher score means more risk. It also returns a decision: allow, step_up, review or block. Your auth provider already knows facts that make the score more accurate. Examples are whether the user verified the email and whether the user passed a second factor. auth_context is the part of the assessment request that carries these facts. An adapter in each NoResponse SDK builds it from the claims that you verified. Any provider that issues OpenID Connect ID tokens works with the generic adapter, fromOIDC. Examples are Okta, Ping Identity, OneLogin, Zitadel, Authentik and Google. Supabase, Firebase, Amazon Cognito, Microsoft Entra ID, Keycloak and Ory each have a dedicated adapter that reads their own claims. They are listed below. Clerk, WorkOS and Auth0 have their own pages. Your backend does four things:
  1. Verifies the ID token: its signature, issuer and audience.
  2. Calls the API of your provider: gets the account age.
  3. fromOIDC: turns the claims and the overrides into auth_context.
  4. assessments.create: NoResponse returns the assessment.

What fromOIDC reads

fromOIDC reads only the standard claims. Their meaning is the same at every provider: It never reads email_verified, amr, act or any organization or role claim from the token, because providers fill those claims differently. Pass each fact that you can prove as an override. The adapter sends it as given.

Where each fact comes from

When your provider cannot supply a fact, leave it out of auth_context. An absent fact is not false, and it does not move the score.

Install

TypeScript
Set these variables in the environment of your backend:
  • OIDC_ISSUER: the issuer URL of your provider, exactly as its discovery document gives it, such as https://example.okta.com/oauth2/default.
  • OIDC_CLIENT_ID: the client ID of your application. It is the audience of the ID token.
  • NORESPONSE_API_KEY: your NoResponse key, with the assess scope. The TypeScript and Python clients read it themselves. The Go client takes it as an argument. The quickstart tells you how to get one.

Assess the new account

Assess each new account once. Do it on the first authenticated request for a sub that your database does not know yet. Do it before you create your own records for the account or give it access. The API answers within 3 seconds. The ID token is the id_token in the token response. Your callback route receives it after the login redirect.
Your callback must compare the nonce claim with the nonce that it sent. openid-client and Authlib do this for you. The go-oidc Verify does not, so compare tok.Nonce with the nonce that you stored before the redirect.
Your login library can return the verified claims, as openid-client, Authlib and go-oidc do. Then pass those claims to the adapter and skip step 1. Each sample reads the discovery document on first use, not at startup. So a provider outage fails the assessment of that new account, and your error path handles it. The outage does not stop your server from starting. The first argument to fromOIDC names your provider in auth_context.provider. It is a lowercase slug of 2 to 32 letters, digits, dots, underscores or hyphens, such as okta. accountCreatedAt is the creation time of the user from the user API of your provider, as an RFC 3339 string. Leave it empty when you do not have it.
TypeScript
ip is the public address of the client. Behind a load balancer, read it from the header that the load balancer sets, such as the last address that it appended to X-Forwarded-For. Never read it from a value that the client controls. The sub of the token is the account_id. It is stable, and the API of your provider takes it when you act on the result later. The flow is account creation, so NoResponse waits up to 3000 ms for its full set of sources. To wait only for the sources that answer within 500 ms, send depth: "quick". The other sources still finish later, and their results reach you through the webhook.
The adapter sends overrides as givenThe adapter tests the claims that it maps against the limits of the API, and drops a value that does not fit. It does not test overrides. An override replaces the mapped value, false included. The adapter skips a null or undefined override.Write an override only from a verified token or from a record that your backend read from the provider. The adapter sets verified: true and transport: "backend" to say that your backend did the verification.

Override names

Overrides use the auth_context field names:
  • In TypeScript, camelCase: emailVerified, accountCreatedAt, mfa.ageSeconds.
  • In Python and Go, snake_case keys: email_verified, account_created_at, mfa.age_seconds.
Times are RFC 3339 strings. The field reference lists every field.

Act on the answer

The answer starts with assessment.account, the one current risk of the account. Your code acts on assessment.account.risk.decision, the decision for the flow that you sent:
  • allow: let the account in.
  • step_up: ask for more proof first, such as a second factor.
  • review: hold the account until your team looks at it.
  • block: refuse the account. NoResponse answers block after you set a block threshold and the score reaches it.
  • Any other value: treat it as review.
assessment.account.risk.score and assessment.account.risk.reasons tell you why. assessment.detail holds the facts of this assessment, such as its status, the sources that did not answer and the summary facts.
  • assessment.account is absent while the first assessment of the account is still pending. Apply your fallback. Then take the final assessment from the webhook when it arrives.
  • If the call fails or times out, apply the same fallback.
Turn an assessment into a decision gives a full rule set to copy.

What the adapter sends

This is the auth_context for a new Okta user with a verified email who passed a second factor at login a minute ago:
auth_context for a new Okta user
That context fires three reasons. account.risk.reasons lists each one with its weight:
  • auth_new_account raises the score for any new account.
  • auth_email_verified and auth_mfa_recent lower it. They make this account different from one with nothing verified.

Dedicated adapters

These adapters read the claims of each provider as well as the standard ones. Every SDK has them. Each adapter takes the verified claims and an optional overrides object, in the same way as fromOIDC, but without the slug.
  • Each Go function takes the claims and the overrides as map[string]any. Pass nil for no overrides.
  • Times in overrides are RFC 3339 strings, so convert the milliseconds of a provider first.
TypeScript fromSupabase, Python from_supabase, Go FromSupabaseClaims.
  • Reads from the claims: the access token, verified with the JWKS or JWT secret of your project. It reads session_id and role, a second factor from aal and amr, and an anonymous user as assurance_level: low.
  • Pass as overrides: email_verified and phone_verified, true when the user has email_confirmed_at or phone_confirmed_at. account_created_at from the created_at of the user.
  • Walkthrough.
TypeScript fromFirebase, Python from_firebase, Go FromFirebaseClaims.
  • Reads from the claims: the ID token claims from verifyIdToken in the Admin SDK. It reads firebase.tenant as tenant_id, firebase.sign_in_second_factor as mfa, and an anonymous login as assurance_level: low. Custom claims never change the mapping.
  • Pass as overrides: email_verified from emailVerified on the user record from getUser, because custom claims can overwrite the email_verified of the token. phone_verified: true when the user has a phoneNumber. account_created_at from metadata.creationTime.
  • Walkthrough.
TypeScript fromCognito, Python from_cognito, Go FromCognitoClaims.
  • Reads from the claims: a user pool ID token or access token. It reads client_id as the audience of an access token, origin_jti as session_id, cognito:groups as roles, scope, and amr. Claims that a pre token generation Lambda adds never change the mapping.
  • Pass as overrides: email_verified and phone_verified from the email_verified and phone_number_verified of the ID token. Use AdminGetUser instead if a pre token generation Lambda can change them. account_created_at from UserCreateDate in AdminGetUser.
TypeScript fromEntra, Python from_entra, Go FromEntraClaims.
  • Reads from the claims: a v1.0 or v2.0 ID or access token. It reads tid as tenant_id, uti as token_id, roles, scp, and amr when the token carries it. mfa in amr sets mfa.present.
  • Pass as overrides: account_created_at from createdDateTime on the Microsoft Graph user.
TypeScript fromKeycloak, Python from_keycloak, Go FromKeycloakClaims.
  • Reads from the claims: an ID or access token. It reads sid or session_state, organization, realm_access.roles and scope. It reads the impersonator claim from the impersonator mappers of Keycloak. It reads amr from the AMR mapper, where mfa or factors of two kinds set mfa.present.
  • Pass as overrides: email_verified from the email_verified of the token. account_created_at from createdTimestamp on the admin API user. session_created_at from start on the admin API session. Both are in milliseconds.
TypeScript fromOry, Python from_ory, Go FromOrySession.
  • Reads from the session: the session object from toSession, not a JWT. It reads the identity id, the session id and authenticated_at. It reads issued_at as session_created_at and the created_at of the identity as account_created_at. It reads the second factor from the assurance level and the authentication methods.
  • Pass as overrides: issuer, required. This is the URL of your Ory project, which the session does not carry. email_verified from verified on the verifiable address of the identity.
TypeScript fromOryOAuth2, Python from_ory_oauth2, Go FromOryOAuth2Claims.
  • Reads from the claims: the ID token. It reads the standard claims, and amr, where factors of two kinds set mfa.present.
  • Pass as overrides: the facts in the table above.
TypeScript fromOIDC, Python from_oidc, Go FromOIDCClaims.
  • Reads from the claims: the standard claims of the ID token. It takes the provider slug first.
  • Pass as overrides: every fact, as this page shows.

Next steps

Turn an assessment into a decision

Rules for allow, step_up, review and block.

Pending, retries and idempotency

What a pending answer carries and how retries work.

Pass your auth session

Every auth_context field and the six auth reason codes.