Auth Flows (no SDK)
The endpoints on this page are the ones the Bridge SDKs call under the hood. If there is no SDK for your language, or you want full control over your login UI, you can drive every flow yourself with plain HTTP: no redirects to the hosted login, no cookies, everything as JSON request/response pairs.
Every flow ends the same way: you hold a short-lived login session token and a chosen workspace (the API calls it a tenant; you manage your app, its allowed origins, and its workspaces in the Control Center), and you exchange them for OAuth tokens with POST /auth/token/direct. From there on you are in standard token territory: refresh, verification, and revocation are covered on the Authentication page.
Conventions that apply to every call on this page:
- SDK mode. Calls that carry
"mode": "sdk"in the body tell The Bridge to return everything as JSON instead of setting cookies and redirecting. That is the mode you want here. - Origin checks. Requests with
"mode": "sdk"must send anOriginheader that matches one of your app’s allowed origins (configured in Control Center). Browsers add it automatically; when testing with curl you must add it yourself, so the curl examples below include it. - The session token. Credential endpoints return
session, a short-lived JWT representing the half-finished login, together withexpires(its expiry hint) andmfaState. It is not an access token. Store it (memory is fine) and pass it to the next step. mfaStatetells you what the next step is:DISABLED: no MFA required, go straight to the token exchange.REQUIRED: the user must pass an MFA challenge first.SETUP: MFA is enabled for your app but the user has no phone enrolled yet, run MFA setup first.COMPLETED: the MFA step has been passed for this session.
tenantUsersis the list of workspaces the user can enter. Each entry’sidis thetenantUserIdyou pass to the token exchange. If there is exactly one, select it automatically; if there are several, show a workspace picker.
Exchange the session for tokens
Section titled “Exchange the session for tokens”Every login flow (password, magic link, passkey) finishes with this call.
Direct token exchange
Section titled “Direct token exchange”Exchange a login session and a chosen workspace for OAuth tokens. Requires the session’s mfaState to be COMPLETED or DISABLED, and tenantUserId must be one of the session user’s tenantUsers entries.
HTTP Request
Section titled “HTTP Request”POST https://api.thebridge.dev/auth/token/direct
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
session | string | Required | The session JWT from the credential step |
tenantUserId | string | Required | The id of the chosen tenantUsers entry |
appId | string | Required | Your app ID. Must match the app the session was issued for |
scope | string | Optional | Defaults to openid profile email onboarding tenant |
mode | string | Required | Set to sdk |
Response HTTP 200
Section titled “Response HTTP 200”The full token set plus user_profile (the decoded id_token). Store access_token, refresh_token, and id_token; the login session can be discarded. HTTP 401 when the session is invalid, MFA is not completed, or the tenant user does not belong to the session’s user.
Request example
curl --request POST 'https://api.thebridge.dev/auth/token/direct' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
"session": "SESSION_JWT",
"tenantUserId": "63d25a9e0796d40008680f9a",
"appId": "YOUR_APP_ID",
"scope": "openid profile email onboarding tenant",
"mode": "sdk"
}'Response example:
{
"access_token": "XXXX",
"refresh_token": "XXXX",
"token_type": "Bearer",
"expires_in": 3600,
"id_token": "XXXX",
"user_profile": {
"sub": "63d25a9e0796d40008680f9a",
"name": "John Doe",
"preferred_username": "john@example.com",
"email": "john@example.com",
"email_verified": true,
"tenant_id": "63d25a9e0796d40008680f96",
"tenant_name": "Johns Family"
}
}
Signup
Section titled “Signup”Create a new workspace with the user as its owner. The sequence:
POST /auth/auth/signupwith the user’s email and name. The Bridge sends the user a verification email.- The user clicks the verification link and sets up their credentials.
- The user logs in through any of the login flows below.
Nothing needs to be stored between steps; the flow hands over to a normal login.
Create a signup
Section titled “Create a signup”Self-signup must be enabled for your app, otherwise the call fails with HTTP 403.
HTTP Request
Section titled “HTTP Request”POST https://api.thebridge.dev/auth/auth/signup
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
email | string | Required | The new user's email address |
firstName | string | Optional | Given name |
lastName | string | Optional | Family name |
appId | string | Required | Your app ID |
mode | string | Required | Set to sdk |
Response HTTP 200
Section titled “Response HTTP 200”{ "success": true, "message": "Check your email to verify your account" }
HTTP 403 when tenant self-signup is not enabled for the app or the origin is not allowed.
Request example
curl --request POST 'https://api.thebridge.dev/auth/auth/signup' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
"email": "john@example.com",
"firstName": "John",
"lastName": "Doe",
"appId": "YOUR_APP_ID",
"mode": "sdk"
}'Response example:
{
"success": true,
"message": "Check your email to verify your account"
}
Email and password login
Section titled “Email and password login”The classic flow. The sequence:
- (Optional)
POST /auth/auth/credentialsConfigwith the email address, to learn which login methods this user has and render the right UI. POST /auth/auth/authenticatewith email and password. Storesession, notemfaState, and keeptenantUsers.- If
mfaStateisREQUIREDorSETUP, run the matching MFA flow. It returns an updatedsession; use that from here on. POST /auth/token/directwith the session and the chosentenantUsers[i].id. Store the returned tokens.
Get credentials config
Section titled “Get credentials config”Check which authentication methods are available for a username: password, passkeys, and any federation connections (SSO). Use it after the user types their email to decide whether to show a password field, a passkey button, or an SSO redirect.
HTTP Request
Section titled “HTTP Request”POST https://api.thebridge.dev/auth/auth/credentialsConfig
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | Required | The user's email address |
appId | string | Required | Your app ID |
mode | string | Required | Set to sdk |
Response HTTP 200
Section titled “Response HTTP 200”{ hasPassword, hasPasskeys, federationConnections }. HTTP 401 for unknown users.
Request example
curl --request POST 'https://api.thebridge.dev/auth/auth/credentialsConfig' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
"username": "john@example.com",
"appId": "YOUR_APP_ID",
"mode": "sdk"
}'Response example:
{
"hasPassword": true,
"hasPasskeys": false,
"federationConnections": [
{ "id": "64a1f00b8a1c4d0008b1e001", "type": "saml", "name": "Acme SSO" }
]
}
Authenticate with password
Section titled “Authenticate with password”Submit the user’s email and password. On success you get the login session and the user’s workspaces.
HTTP Request
Section titled “HTTP Request”POST https://api.thebridge.dev/auth/auth/authenticate
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | Required | The user's email address |
password | string | Required | The user's password |
appId | string | Required | Your app ID |
mode | string | Required | Set to sdk |
Response HTTP 200
Section titled “Response HTTP 200”| Field | Type | Description |
|---|---|---|
| session | string | The login session JWT. Carry it to the next step |
| expires | number | Session expiry hint |
| mfaState | string | DISABLED, REQUIRED, or SETUP |
| tenantUsers | array | The user’s workspaces: { id, username, fullName, tenant: { id, name, logo } } |
HTTP 401 for wrong credentials.
Between steps, keep
sessionand the chosentenantUsers[i].id. Nothing else is needed.
Request example
curl --request POST 'https://api.thebridge.dev/auth/auth/authenticate' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
"username": "john@example.com",
"password": "SECRET",
"appId": "YOUR_APP_ID",
"mode": "sdk"
}'Response example:
{
"session": "XXXX",
"expires": 604800,
"mfaState": "DISABLED",
"tenantUsers": [
{
"id": "63d25a9e0796d40008680f9a",
"username": "john@example.com",
"fullName": "John Doe",
"tenant": {
"id": "63d25a9e0796d40008680f96",
"name": "Johns Family",
"logo": ""
}
}
]
}
Magic link
Section titled “Magic link”Passwordless login over email. The sequence:
POST /auth/auth/magic-linkwith the user’s email and asuccessUrlpointing back into your app. The Bridge emails the user a login link.- The user clicks the link and lands on your
successUrlwith?bridge_magic_link_token=TOKENappended. POST /auth/auth/magic-link/authenticatewith that token. You get the samesession,mfaState, andtenantUsersas a password login.- If
mfaStateisREQUIREDorSETUP, run the matching MFA flow. POST /auth/token/directwith the session and the chosen workspace.
Nothing needs to be stored between steps 1 and 3: the token in the link is a signed JWT that carries the app and the user.
Request a magic link
Section titled “Request a magic link”Send a login link to the user’s email address.
HTTP Request
Section titled “HTTP Request”POST https://api.thebridge.dev/auth/auth/magic-link
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | Required | The user's email address |
successUrl | string | Optional | The page in your app the emailed link should land on. The magic link token is appended as ?bridge_magic_link_token=TOKEN |
appId | string | Required | Your app ID |
mode | string | Required | Set to sdk |
Response HTTP 200
Section titled “Response HTTP 200”{ "expiresIn": 900000 }, the link’s validity in milliseconds. Unknown email addresses also get HTTP 200 with a faked expiresIn, so the endpoint cannot be used to discover which emails have accounts.
Always pass
successUrlwhen you build your own UI. Without it the link is built for the hosted login flow instead: it points at/auth/magic-link/login?t=TOKENon your origin (or on the hosted login if theOriginheader is not an allowed origin).
Request example
curl --request POST 'https://api.thebridge.dev/auth/auth/magic-link' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
"username": "john@example.com",
"successUrl": "https://your-app.example.com/login/magic-link",
"appId": "YOUR_APP_ID",
"mode": "sdk"
}'Response example:
{
"expiresIn": 900000
}
Authenticate with the magic link token
Section titled “Authenticate with the magic link token”When the user lands on your successUrl, read bridge_magic_link_token from the query string and exchange it for a login session. No appId or mode is needed: the token itself identifies the app and the user.
HTTP Request
Section titled “HTTP Request”POST https://api.thebridge.dev/auth/auth/magic-link/authenticate
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
token | string | Required | The bridge_magic_link_token value from the URL |
Response HTTP 200
Section titled “Response HTTP 200”| Field | Type | Description |
|---|---|---|
| session | string | The login session JWT. Carry it to the next step |
| expires | number | Session expiry hint |
| mfaState | string | DISABLED, REQUIRED, or SETUP |
| tenantUsers | array | The user’s workspaces: { id, username, fullName, tenant: { id, name, logo } } |
HTTP 401 when the token is invalid or expired; send a fresh link.
The magic link JWT and the login session are different tokens. Exchange the link token here first, then use the returned
sessioneverywhere else.
Request example
curl --request POST 'https://api.thebridge.dev/auth/auth/magic-link/authenticate' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
"token": "MAGIC_LINK_TOKEN"
}'Response example:
{
"session": "XXXX",
"expires": 604800,
"mfaState": "DISABLED",
"tenantUsers": [
{
"id": "63d25a9e0796d40008680f9a",
"username": "john@example.com",
"fullName": "John Doe",
"tenant": {
"id": "63d25a9e0796d40008680f96",
"name": "Johns Family",
"logo": ""
}
}
]
}
Passkeys
Section titled “Passkeys”WebAuthn login. Both ceremonies follow the same shape: fetch options from The Bridge, run the browser ceremony (navigator.credentials.create() or .get()), and post the resulting credential back for verification. In SDK mode the server’s challenge comes back in the options response as sdkChallengeToken (instead of a cookie), and you must echo it on the verify call together with sdkOrigin, your app’s origin as the browser sees it.
Registration (the user proves email ownership first):
POST /auth/auth/passkeys/request-setup-linkwith the user’s email. The Bridge emails a setup link that lands on/auth/setup-passkey/PASSKEY_SETUP_TOKENon your origin (when the requestOriginis one of your allowed origins). Serve that route in your app.- On that page,
GET /auth/auth/passkeys/registration-options?passkeySetupToken=.... PullsdkChallengeTokenout of the response; the rest is standard WebAuthn creation options. - Run
navigator.credentials.create()with the options and serialize the credential to JSON. POST /auth/auth/passkeys/verify-registration?passkeySetupToken=...with the credential JSON plusappId,sdkChallengeToken, andsdkOrigin. Response:{ "verified": true }.
Login:
GET /auth/auth/passkeys/authentication-optionswith your app ID in thex-app-idheader. Pull outsdkChallengeToken.- Run
navigator.credentials.get()with the options and serialize the assertion to JSON. POST /auth/auth/passkeys/verify-authenticationwith the assertion plusmode,appId,sdkChallengeToken, andsdkOrigin. Same response as a password login:session,mfaState,tenantUsers.- If
mfaStateisREQUIREDorSETUP, run the matching MFA flow, thenPOST /auth/token/direct.
The WebAuthn JSON encoding (base64url
rawId,clientDataJSON, and friends) is fiddly to hand-roll. The@simplewebauthn/browserpackage’sstartRegistration/startAuthenticationproduce exactly the JSON these endpoints expect; The Bridge verifies with@simplewebauthn/server.
Request a passkey setup link
Section titled “Request a passkey setup link”Email the user a link to register a new passkey device. Registration is gated behind this email round-trip so that only someone with access to the inbox can add a passkey.
HTTP Request
Section titled “HTTP Request”POST https://api.thebridge.dev/auth/auth/passkeys/request-setup-link
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | Required | The user's email address |
appId | string | Required | Your app ID |
mode | string | Required | Set to sdk |
Response HTTP 200
Section titled “Response HTTP 200”{ "success": true }. The emailed link points at /auth/setup-passkey/PASSKEY_SETUP_TOKEN on your origin when the request Origin is allowed, otherwise on the hosted login.
Request example
curl --request POST 'https://api.thebridge.dev/auth/auth/passkeys/request-setup-link' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
"username": "john@example.com",
"appId": "YOUR_APP_ID",
"mode": "sdk"
}'Response example:
{
"success": true
}
Get registration options
Section titled “Get registration options”Generate the WebAuthn creation options for a new passkey. Identify your app with the x-app-id header; that is what switches the endpoint into SDK mode and makes it return sdkChallengeToken in the body.
HTTP Request
Section titled “HTTP Request”GET https://api.thebridge.dev/auth/auth/passkeys/registration-options?passkeySetupToken=PASSKEY_SETUP_TOKEN
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
passkeySetupToken | string | Required | The token from the setup link URL |
Headers
| Parameter | Type | Required | Description |
|---|---|---|---|
x-app-id | string | Required | Your app ID |
Response HTTP 200
Section titled “Response HTTP 200”Standard WebAuthn PublicKeyCredentialCreationOptions JSON (challenge, rp, user, pubKeyCredParams, timeout, …) plus sdkChallengeToken. Remove sdkChallengeToken before handing the rest to navigator.credentials.create(), and keep it for the verify call. HTTP 401 when the setup token is invalid or expired.
Request example
curl --request GET 'https://api.thebridge.dev/auth/auth/passkeys/registration-options?passkeySetupToken=PASSKEY_SETUP_TOKEN' \
--header 'x-app-id: YOUR_APP_ID' \
--header 'Origin: https://your-app.example.com'Response example:
{
"challenge": "y5PDGD3PD9EBhgGGDf-pTP...",
"rp": {
"name": "Nblocks",
"id": "your-app.example.com"
},
"user": {
"id": "63d25a9e0796d40008680f99",
"name": "john@example.com",
"displayName": "john@example.com"
},
"pubKeyCredParams": [
{ "alg": -7, "type": "public-key" },
{ "alg": -257, "type": "public-key" }
],
"timeout": 60000,
"attestation": "none",
"excludeCredentials": [],
"sdkChallengeToken": "XXXX"
}
Verify the registration
Section titled “Verify the registration”Submit the credential produced by navigator.credentials.create(). The body is the serialized WebAuthn registration response spread at the top level, with the SDK fields added next to it.
HTTP Request
Section titled “HTTP Request”POST https://api.thebridge.dev/auth/auth/passkeys/verify-registration?passkeySetupToken=PASSKEY_SETUP_TOKEN
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
passkeySetupToken | string | Required | The same token used for the options call |
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Required | From the WebAuthn credential JSON |
rawId | string | Required | From the WebAuthn credential JSON (base64url) |
type | string | Required | public-key |
response | object | Required | The attestation: { clientDataJSON, attestationObject, transports } |
clientExtensionResults | object | Optional | From the WebAuthn credential JSON |
appId | string | Required | Your app ID. Its presence is what selects SDK mode here |
sdkChallengeToken | string | Required | Echoed from the options response |
sdkOrigin | string | Required | Your app's origin exactly as the browser sends it, e.g. https://your-app.example.com |
Response HTTP 200
Section titled “Response HTTP 200”{ "verified": true }. The device is now registered and shows up as hasPasskeys: true in credentials config. HTTP 401 when the challenge, setup token, or attestation does not verify.
Registration only stores the device. To log the user in afterwards, run the passkey login ceremony below.
Request example
curl --request POST 'https://api.thebridge.dev/auth/auth/passkeys/verify-registration?passkeySetupToken=PASSKEY_SETUP_TOKEN' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
"id": "CREDENTIAL_ID",
"rawId": "CREDENTIAL_ID",
"type": "public-key",
"response": {
"clientDataJSON": "BASE64URL",
"attestationObject": "BASE64URL",
"transports": ["internal"]
},
"clientExtensionResults": {},
"appId": "YOUR_APP_ID",
"sdkChallengeToken": "XXXX",
"sdkOrigin": "https://your-app.example.com"
}'Response example:
{
"verified": true
}
Get authentication options
Section titled “Get authentication options”Generate the WebAuthn request options for a passkey login. Identify your app with the x-app-id header; that switches the endpoint into SDK mode and makes it return sdkChallengeToken in the body. No user identifier is needed: allowCredentials is empty, so the browser offers whatever passkeys it holds for this site.
HTTP Request
Section titled “HTTP Request”GET https://api.thebridge.dev/auth/auth/passkeys/authentication-options
Headers
| Parameter | Type | Required | Description |
|---|---|---|---|
x-app-id | string | Required | Your app ID |
Response HTTP 200
Section titled “Response HTTP 200”Standard WebAuthn PublicKeyCredentialRequestOptions JSON (challenge, rpId, timeout, userVerification, allowCredentials) plus sdkChallengeToken. Remove sdkChallengeToken before handing the rest to navigator.credentials.get(), and keep it for the verify call.
Request example
curl --request GET 'https://api.thebridge.dev/auth/auth/passkeys/authentication-options' \
--header 'x-app-id: YOUR_APP_ID' \
--header 'Origin: https://your-app.example.com'Response example:
{
"challenge": "meWpAY-x02Yz3PtRAKQXH1...",
"timeout": 60000,
"rpId": "your-app.example.com",
"userVerification": "preferred",
"allowCredentials": [],
"sdkChallengeToken": "XXXX"
}
Verify the assertion
Section titled “Verify the assertion”Submit the assertion produced by navigator.credentials.get(). On success you get the same login session shape as a password login; continue with MFA (if required) and the token exchange.
HTTP Request
Section titled “HTTP Request”POST https://api.thebridge.dev/auth/auth/passkeys/verify-authentication
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Required | From the WebAuthn assertion JSON |
rawId | string | Required | From the WebAuthn assertion JSON (base64url). Identifies the device |
type | string | Required | public-key |
response | object | Required | The assertion: { clientDataJSON, authenticatorData, signature, userHandle } |
clientExtensionResults | object | Optional | From the WebAuthn assertion JSON |
mode | string | Required | Set to sdk |
appId | string | Required | Your app ID |
sdkChallengeToken | string | Required | Echoed from the options response |
sdkOrigin | string | Required | Your app's origin exactly as the browser sends it |
Response HTTP 200
Section titled “Response HTTP 200”| Field | Type | Description |
|---|---|---|
| session | string | The login session JWT. Carry it to the next step |
| expires | number | Session expiry hint |
| mfaState | string | DISABLED, REQUIRED, or SETUP |
| tenantUsers | array | The user’s workspaces: { id, username, fullName, tenant: { id, name, logo } } |
HTTP 401 when the signature or challenge does not verify, or when no passkey is registered for this device in this environment (error code NBLOCKS_PASSKEY_NOT_REGISTERED; offer another login method and passkey setup).
Request example
curl --request POST 'https://api.thebridge.dev/auth/auth/passkeys/verify-authentication' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
"id": "CREDENTIAL_ID",
"rawId": "CREDENTIAL_ID",
"type": "public-key",
"response": {
"clientDataJSON": "BASE64URL",
"authenticatorData": "BASE64URL",
"signature": "BASE64URL",
"userHandle": "BASE64URL"
},
"clientExtensionResults": {},
"mode": "sdk",
"appId": "YOUR_APP_ID",
"sdkChallengeToken": "XXXX",
"sdkOrigin": "https://your-app.example.com"
}'Response example:
{
"session": "XXXX",
"expires": 604800,
"mfaState": "DISABLED",
"tenantUsers": [
{
"id": "63d25a9e0796d40008680f9a",
"username": "john@example.com",
"fullName": "John Doe",
"tenant": {
"id": "63d25a9e0796d40008680f96",
"name": "Johns Family",
"logo": ""
}
}
]
}
MFA setup
Section titled “MFA setup”When a login returns mfaState: "SETUP", MFA is enforced for your app but the user has no phone number enrolled yet. The token exchange will refuse the session until MFA is completed, so enroll a phone first:
POST /auth/auth/startMfaUserSetupwith the phone number and the session. The Bridge texts a 6-digit code to that phone and returns a newsessioncarrying the pending code. Use the new session from here on.POST /auth/auth/finishMfaUserSetupwith the code the user typed. The response contains a one-timebackupCode(the recovery code) and a session withmfaState: "COMPLETED".- Show
backupCodeto the user once and tell them to store it safely; it is their only way back in if they lose the phone. It is not returned again. POST /auth/token/directwith the latest session.
Every MFA endpoint returns a fresh
session. Always replace the one you stored with the one from the latest response; the old one no longer carries the right MFA state.
Start MFA setup
Section titled “Start MFA setup”Enroll a phone number: The Bridge texts a 6-digit verification code to it. Only valid while the session’s mfaState is SETUP.
HTTP Request
Section titled “HTTP Request”POST https://api.thebridge.dev/auth/auth/startMfaUserSetup
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
phoneNumber | string | Required | The phone number to enroll, in international format, e.g. +46700000000 |
session | string | Required | The session JWT from the login step |
appId | string | Required | Your app ID |
mode | string | Required | Set to sdk |
Response HTTP 200
Section titled “Response HTTP 200”{ session, expires, mfaState: "SETUP" }. The returned session embeds the pending code; pass it to the finish call. HTTP 401 when the session is invalid.
Request example
curl --request POST 'https://api.thebridge.dev/auth/auth/startMfaUserSetup' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
"phoneNumber": "+46700000000",
"session": "SESSION_JWT",
"appId": "YOUR_APP_ID",
"mode": "sdk"
}'Response example:
{
"session": "XXXX",
"expires": 604800,
"mfaState": "SETUP"
}
Finish MFA setup
Section titled “Finish MFA setup”Verify the texted code and complete the enrollment. Use the session returned by the start call.
HTTP Request
Section titled “HTTP Request”POST https://api.thebridge.dev/auth/auth/finishMfaUserSetup
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
mfaCode | string | Required | The 6-digit code the user received by SMS |
session | string | Required | The session JWT returned by startMfaUserSetup |
appId | string | Required | Your app ID |
mode | string | Required | Set to sdk |
Response HTTP 200
Section titled “Response HTTP 200”{ backupCode, session, expires, mfaState: "COMPLETED" }. HTTP 401 when the code is wrong (error code NBLOCKS_INVALID_MFA_CODE) or the session is invalid.
backupCodeis the user’s one-time recovery code. Display it once, prompt the user to store it, and never log it. It is whatresetUserMfaSetupasks for when the phone is lost.
Request example
curl --request POST 'https://api.thebridge.dev/auth/auth/finishMfaUserSetup' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
"mfaCode": "123456",
"session": "SESSION_JWT",
"appId": "YOUR_APP_ID",
"mode": "sdk"
}'Response example:
{
"backupCode": "XXXX",
"session": "XXXX",
"expires": 604800,
"mfaState": "COMPLETED"
}
MFA challenge
Section titled “MFA challenge”When a login returns mfaState: "REQUIRED", a 6-digit code has already been texted to the user’s enrolled phone as part of the credential step. The sequence:
- Ask the user for the code.
POST /auth/auth/commitMfaCodewith the code and the session. You get a fresh session withmfaState: "COMPLETED".POST /auth/token/directwith that session.
Two side paths:
- The text never arrived.
POST /auth/auth/resendMfaCodesends a new code and returns a fresh session tied to it; use that session for the commit call. The old code stops working. - The phone is lost.
POST /auth/auth/resetUserMfaSetupwith the user’s recovery code wipes the enrollment and returns a session withmfaState: "SETUP"; run MFA setup again with the new phone number.
Commit the MFA code
Section titled “Commit the MFA code”Verify the texted code and mark the session’s MFA step as passed. Only valid while the session’s mfaState is REQUIRED.
HTTP Request
Section titled “HTTP Request”POST https://api.thebridge.dev/auth/auth/commitMfaCode
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
mfaCode | string | Required | The 6-digit code the user received by SMS |
session | string | Required | The session JWT from the login step (or from resendMfaCode) |
appId | string | Required | Your app ID |
mode | string | Required | Set to sdk |
Response HTTP 200
Section titled “Response HTTP 200”{ session, expires, mfaState: "COMPLETED" }. Use the returned session for the token exchange. HTTP 401 when the code is wrong (error code NBLOCKS_INVALID_MFA_CODE) or the session is invalid.
Request example
curl --request POST 'https://api.thebridge.dev/auth/auth/commitMfaCode' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
"mfaCode": "123456",
"session": "SESSION_JWT",
"appId": "YOUR_APP_ID",
"mode": "sdk"
}'Response example:
{
"session": "XXXX",
"expires": 604800,
"mfaState": "COMPLETED"
}
Resend the MFA code
Section titled “Resend the MFA code”Text a new code to the enrolled phone. Only valid while the session’s mfaState is REQUIRED.
HTTP Request
Section titled “HTTP Request”POST https://api.thebridge.dev/auth/auth/resendMfaCode
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
session | string | Required | The session JWT from the login step |
appId | string | Required | Your app ID |
mode | string | Required | Set to sdk |
Response HTTP 200
Section titled “Response HTTP 200”{ session, expires, mfaState: "REQUIRED" }. The returned session is tied to the new code; the previous code no longer validates. HTTP 401 when the session is invalid.
Request example
curl --request POST 'https://api.thebridge.dev/auth/auth/resendMfaCode' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
"session": "SESSION_JWT",
"appId": "YOUR_APP_ID",
"mode": "sdk"
}'Response example:
{
"session": "XXXX",
"expires": 604800,
"mfaState": "REQUIRED"
}
Reset MFA with a recovery code
Section titled “Reset MFA with a recovery code”For users who lost their phone. Submits the one-time backupCode from enrollment, wipes the old phone number, and drops the session back to mfaState: "SETUP" so the user can enroll a new one. Only valid while the session’s mfaState is REQUIRED.
HTTP Request
Section titled “HTTP Request”POST https://api.thebridge.dev/auth/auth/resetUserMfaSetup
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
backupCode | string | Required | The recovery code handed out by finishMfaUserSetup |
session | string | Required | The session JWT from the login step |
appId | string | Required | Your app ID |
mode | string | Required | Set to sdk |
Response HTTP 200
Section titled “Response HTTP 200”{ session, expires, mfaState: "SETUP" }. Continue with MFA setup using the returned session; finishing it issues a new recovery code. HTTP 401 when the recovery code or session is invalid.
Request example
curl --request POST 'https://api.thebridge.dev/auth/auth/resetUserMfaSetup' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
"backupCode": "RECOVERY_CODE",
"session": "SESSION_JWT",
"appId": "YOUR_APP_ID",
"mode": "sdk"
}'Response example:
{
"session": "XXXX",
"expires": 604800,
"mfaState": "SETUP"
}
Password reset
Section titled “Password reset”The classic email round-trip. The sequence:
POST /auth/auth/passwordwith the user’s email. The Bridge emails a reset link.- The link lands on
/auth/set-password/RESET_TOKEN?flow=forgoton your origin (when the requestOriginis one of your allowed origins, otherwise on the hosted login). Serve that route in your app and read the token from the path. - (Optional)
GET /auth/auth/password/token/RESET_TOKENto check the token is still valid before rendering the form. PUT /auth/auth/passwordwith the token and the new password. The token is destroyed on use.- The user logs in again through any login flow.
Unlike the rest of this page, the password endpoints identify your app with the
x-app-idheader and have nomodefield. TheOriginheader is still checked against your allowed origins.
Request a password reset
Section titled “Request a password reset”Send the reset email.
HTTP Request
Section titled “HTTP Request”POST https://api.thebridge.dev/auth/auth/password
Headers
| Parameter | Type | Required | Description |
|---|---|---|---|
x-app-id | string | Required | Your app ID |
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | Required | The user's email address |
Response HTTP 200
Section titled “Response HTTP 200”Empty body. Unknown email addresses also get HTTP 200 (no email is sent), so the endpoint cannot be used to discover which emails have accounts.
Request example
curl --request POST 'https://api.thebridge.dev/auth/auth/password' \
--header 'Content-Type: application/json' \
--header 'x-app-id: YOUR_APP_ID' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
"username": "john@example.com"
}'Response example:
HTTP 200 with an empty body.
Validate the reset token
Section titled “Validate the reset token”Check a reset token before showing the new-password form, so expired links get a friendly error instead of a failed submit.
HTTP Request
Section titled “HTTP Request”GET https://api.thebridge.dev/auth/auth/password/token/RESET_TOKEN
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
token | string | Required | The reset token from the emailed link |
Response HTTP 200
Section titled “Response HTTP 200”{ "appId": "...", "valid": true }. HTTP 404 when the token is unknown or expired; HTTP 403 when the Origin is not allowed for the token’s app.
Request example
curl --request GET 'https://api.thebridge.dev/auth/auth/password/token/RESET_TOKEN' \
--header 'Origin: https://your-app.example.com'Response example:
{
"appId": "YOUR_APP_ID",
"valid": true
}
Set the new password
Section titled “Set the new password”Commit the new password using the reset token. The token is single-use: it is destroyed on success.
HTTP Request
Section titled “HTTP Request”PUT https://api.thebridge.dev/auth/auth/password
Headers
| Parameter | Type | Required | Description |
|---|---|---|---|
x-app-id | string | Required | Your app ID |
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
token | string | Required | The reset token from the emailed link |
password | string | Required | The new password |
Response HTTP 200
Section titled “Response HTTP 200”Empty body. HTTP 401 when the token is invalid, expired, or already used. There is no session in the response; send the user to a login flow.
The same emailed reset token also works as
forgotPasswordTokenon the passkey registration endpoints, so your set-password page can offer “register a passkey instead” with the same token.
Request example
curl --request PUT 'https://api.thebridge.dev/auth/auth/password' \
--header 'Content-Type: application/json' \
--header 'x-app-id: YOUR_APP_ID' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
"token": "RESET_TOKEN",
"password": "NEW_SECRET"
}'Response example:
HTTP 200 with an empty body.