Skip to Content
DocumentationDemonstrationStep-up AuthStep 4: Handle the MFA response

Step 4: Handle the MFA response

⏱ 5 min

Process the MFA verification result and implement your security logic


MFA Response Structure

interface IdpAuthResponse { success: boolean; sid?: string; // Step-up Session ID (on success) error?: string; // Error message (on failure) timestamp: number; // Unix timestamp in milliseconds action?: string; // Requested action type }

Basic Handling

const mfaResult = await transcodes.openAuthIdpModal({ resource: 'sensitive_action', action: 'delete', }); if (mfaResult.success && mfaResult.payload[0]?.success) { const { sid, timestamp } = mfaResult.payload[0]; console.log(`Verified at ${new Date(timestamp)}`); if (sid) console.log('Step-up Session ID:', sid); // Proceed with protected action await performSensitiveAction(); } else { // User cancelled or verification failed console.log('MFA verification not completed'); }

MFA Verification Token

After successful MFA, send both the access token and the step-up sid to your backend. The access token proves identity; the sid proves the user just completed step-up for this specific (resource, action).

const mfaResult = await transcodes.openAuthIdpModal({ resource: 'admin_settings', action: 'update', }); if (mfaResult.success && mfaResult.payload[0]?.success) { const stepUpSid = mfaResult.payload[0].sid; const token = await transcodes.token.getAccessToken(); const response = await fetch('/api/admin/settings', { method: 'POST', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ stepUpSid, setting: 'value' }), }); }

Treat sid as untrusted on the client. Your backend must re-verify it against the Transcodes API before performing the protected action — see Server-Side Verification below.


Server-Side Verification

There are two independent server-side checks for a step-up flow, and you should run both:

  1. Access token (JWT) — proves who the caller is. See Server-Side Integration for the full JWT verification setup.
  2. Step-up sid — proves that the same caller just completed a step-up for the requested (resource, action). Verify it against the Transcodes API.

Verifying the step-up sid

Forward the sid from openAuthIdpModal to your backend, then call:

  • GET https://api.transcodesapis.com/v1/auth/temp-session/step-up/{sid}
  • Header: x-transcodes-token: <AUTH_API_TOKEN> — a server-only JWT issued from the Transcodes Console (different from TRANSCODES_TOKEN used by the MCP server). Store it as TRANSCODES_AUTH_API_TOKEN and never expose it to the browser.
// app/api/admin/settings/route.ts (Next.js App Router) import { NextResponse } from 'next/server'; const AUTH_API_TOKEN = process.env.TRANSCODES_AUTH_API_TOKEN!; export async function POST(req: Request) { const accessToken = req.headers .get('authorization') ?.replace(/^Bearer\s+/i, ''); const user = await verifyAccessToken(accessToken); if (!user) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const { stepUpSid, ...body } = await req.json(); if (!stepUpSid) { return NextResponse.json( { error: 'Missing step-up session id' }, { status: 400 }, ); } const response = await fetch( `https://api.transcodesapis.com/v1/auth/temp-session/step-up/${stepUpSid}`, { headers: { 'x-transcodes-token': AUTH_API_TOKEN, }, }, ); if (!response.ok) { return NextResponse.json({ logId: crypto.randomUUID(), success: false, statusCode: response.status, payload: null, error: 'Failed to verify step-up session', }); } const data = await response.json(); // data.payload[0] => { project_id, member_id, action, role } // Optional: assert payload[0].member_id === user.id and payload[0].action matches. await updateSettings(body); return NextResponse.json({ success: true }); }

Example successful response from temp-session/step-up/{sid}:

{ "logId": "01KPP893GSV54TKVST53MV7BB1", "success": true, "statusCode": 200, "payload": [ { "project_id": "ca3da7ff8dd4391d8d80ad01", "member_id": "ca3db8488dd4391d8d80ad04", "action": "read", "role": "admin" } ], "error": null }

Never call the temp-session/step-up/{sid} endpoint from the browser, and never embed AUTH_API_TOKEN in client code — it is a server secret.


Best Practices

Security Recommendations:

  1. Always verify server-side — Re-check the access token (JWT) and the step-up sid against the Transcodes API before performing any sensitive action. Do not trust the client result alone.
  2. Treat sid as one-shot — Forward it to your backend immediately and let Transcodes decide if the step-up is still valid; never cache step-up state in client memory.
  3. Log MFA events — Use trackUserAction() so success and failure both appear in the Audit Logs.

History Log

Record MFA and sensitive actions for audit logs using trackUserAction(). Events appear in the Audit Logs panel

// MFA verification success if (mfaResult.success && mfaResult.payload[0]?.success) { await transcodes.trackUserAction({ tag: 'admin:access', severity: 'high', status: true, metadata: { action: 'read' }, }); } // Sensitive action with webhook alert await transcodes.trackUserAction( { tag: 'payment:transfer', severity: 'high', status: true, metadata: { amount: 1000 } }, { webhookNotification: true } );
trackUserAction tagTypical use (audit log)
admin:accessAdmin panel access
user:deleteAccount deletion
document:deleteDocument deletion
payment:transferFinancial transaction

These tags are for trackUserAction() only. For openAuthIdpModal(), use resource + action (RBAC)—see Modal API.

See Audit API for full trackUserAction reference


Handle account without MFA

If the signed-in member has not set up MFA yet:

const mfaResult = await transcodes.openAuthIdpModal({ resource: 'sensitive_action', action: 'delete', forceStepUp: true, }); if (!mfaResult.success || !mfaResult.payload[0]?.success) { // Redirect to MFA setup in console panel alert('Please set up MFA first'); await transcodes.openAuthConsoleModal(); // Opens console panel for MFA setup }
Last updated on