Wocha Docs

Step-up authentication

Request higher authentication assurance (AAL2/AAL3) via acr_values in OAuth flows

Step-up authentication asks a user who is already signed in to complete an additional verification before accessing a sensitive resource. Wocha implements step-up through standard OAuth acr_values parameters and NIST-aligned ACR/AMR token claims.


When to use step-up

Use step-up when an operation requires stronger assurance than the user's current session provides:

ScenarioRecommended ACR
View account settingsurn:wocha:aal1 (default)
Change password or add MFAurn:wocha:aal2
Transfer funds or delete dataurn:wocha:aal2
Admin key management, privilege escalationurn:wocha:aal3
Adaptive risk triggerDetermined by risk engine

Step-up is distinct from a full re-login. Wocha preserves the existing session where possible and elevates the authenticator assurance level (AAL) to meet the requested ACR.


Requesting ACR levels in OAuth

Pass acr_values in the authorise request. Wocha checks the current session ACR at consent time and redirects to MFA step-up when the session is insufficient.

GET /oauth2/auth?
  client_id=YOUR_CLIENT_ID&
  response_type=code&
  scope=openid+profile&
  redirect_uri=https://app.example.com/callback&
  acr_values=urn:wocha:aal2&
  state=RANDOM_STATE

Multiple values express preference order (first value has highest priority):

acr_values=urn:wocha:aal3 urn:wocha:aal2

Wocha also accepts legacy shorthand values (aal1, aal2, aal3) and maps them to Wocha URNs internally.

  1. Client sends acr_values=urn:wocha:aal2 in the authorise request
  2. User authenticates (if not already signed in)
  3. At consent, Wocha compares currentAcr against the requested value
  4. If insufficient, the user is redirected to step-up with the strongest enrolled factor tenant policy allows
  5. After MFA completion, consent proceeds and tokens include the elevated acr and amr claims

Passkeys and step-up

A passkey sign-in that verified the user (fingerprint, face or device PIN) already satisfies urn:wocha:aal2: it is a phishing-resistant multi-factor cryptographic authenticator, so Wocha does not ask a passkey user for a TOTP or SMS code afterwards. Wocha only counts assertions whose authenticator reported user verification; the hosted UI and @wocha/nextjs request it and refuse assertions without it.

When step-up is required and the user holds a passkey, Wocha asks for the passkey again ("Confirm it's you") instead of a code — including a user who signed in with a password and has no authenticator app. This is the recommended way to confirm a sensitive transaction for passkey users: request acr_values=urn:wocha:aal2 exactly as below and let Wocha pick the passkey. Tenant preferredFactor and allowedFactors still apply, so an organisation that prefers TOTP keeps its TOTP challenge.

Step-up means "AAL2 reached in this session". It does not force a fresh ceremony when the session is already AAL2; contact the platform team if a product needs re-authentication within a time window.


Next.js

Protect a server action or route handler by checking the session ACR, then redirecting to step-up when needed:

import { getSession } from "@wocha/nextjs";
import { requireAal2, ACR_AAL2 } from "@wocha/nextjs";
import { redirect } from "next/navigation";
 
export async function deleteAccount() {
  const session = await getSession();
  if (!session) redirect("/login");
 
  if (!requireAal2(session)) {
    const stepUpUrl = new URL(`${process.env.WOCHA_ISSUER}/oauth2/auth`);
    stepUpUrl.searchParams.set("client_id", process.env.WOCHA_CLIENT_ID!);
    stepUpUrl.searchParams.set("response_type", "code");
    stepUpUrl.searchParams.set("scope", "openid");
    stepUpUrl.searchParams.set("acr_values", ACR_AAL2);
    stepUpUrl.searchParams.set(
      "redirect_uri",
      `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/callback`,
    );
    redirect(stepUpUrl.toString());
  }
 
  // Proceed with account deletion...
}

For middleware-level protection:

import { withWochaAuth } from "@wocha/nextjs/middleware";
import { requireAal3 } from "@wocha/nextjs";
 
export default withWochaAuth(async (req, session) => {
  if (req.nextUrl.pathname.startsWith("/admin/keys")) {
    if (!requireAal3(session)) {
      const url = req.nextUrl.clone();
      url.pathname = "/step-up";
      url.searchParams.set("acr", "urn:wocha:aal3");
      return Response.redirect(url);
    }
  }
});

Express

import express from "express";
import { wochaSession, requireAal2, ACR_AAL2 } from "@wocha/express";
 
const app = express();
 
app.delete("/api/billing/payment-method", wochaSession(), (req, res) => {
  if (!requireAal2(req.wocha.session)) {
    const stepUpUrl = new URL(`${process.env.WOCHA_ISSUER}/oauth2/auth`);
    stepUpUrl.searchParams.set("client_id", process.env.WOCHA_CLIENT_ID!);
    stepUpUrl.searchParams.set("response_type", "code");
    stepUpUrl.searchParams.set("scope", "openid");
    stepUpUrl.searchParams.set("acr_values", ACR_AAL2);
    stepUpUrl.searchParams.set("redirect_uri", process.env.WOCHA_CALLBACK_URL!);
    return res.redirect(302, stepUpUrl.toString());
  }
 
  // Remove payment method...
  res.status(204).end();
});

React

Gate sensitive UI and trigger step-up from the client:

"use client";
 
import { useSession } from "@wocha/react";
 
const ACR_AAL2 = "urn:wocha:aal2";
 
function BillingPanel() {
  const { data: session, status } = useSession();
 
  if (status !== "authenticated") return null;
 
  const acr = session?.acr ?? "urn:wocha:aal1";
  const needsStepUp = !acr.includes("aal2") && !acr.includes("aal3");
 
  if (needsStepUp) {
    const stepUpUrl =
      `${process.env.NEXT_PUBLIC_WOCHA_ISSUER}/oauth2/auth?` +
      `client_id=${process.env.NEXT_PUBLIC_WOCHA_CLIENT_ID}&` +
      `response_type=code&scope=openid&` +
      `acr_values=${encodeURIComponent(ACR_AAL2)}&` +
      `redirect_uri=${encodeURIComponent(window.location.origin + "/callback")}`;
 
    return (
      <div>
        <p>Verify your identity to manage billing.</p>
        <a href={stepUpUrl}>Verify with MFA</a>
      </div>
    );
  }
 
  return <PaymentMethodList />;
}

For adaptive MFA, Wocha handles step-up automatically at consent — your app only needs explicit acr_values for operations that always require a specific assurance level.


ACR and AMR reference

ACR values

ValueLevelDescription
urn:wocha:aal11Single-factor authentication (password, social, or a passkey without user verification)
urn:wocha:aal22Multi-factor authentication (password + TOTP or SMS, or a user-verified passkey)
urn:wocha:aal33Hardware-bound authentication (hardware WebAuthn key as second factor, or a user-verified cross-platform passkey)

Legacy values accepted for compatibility:

LegacyMaps to
aal1urn:wocha:aal1
aal2, urn:mace:incommon:iap:silverurn:wocha:aal2
aal3, urn:mace:incommon:iap:goldurn:wocha:aal3

AMR values (RFC 8176)

AMRAuthentication method
pwdPassword
otpTime-based or email one-time password
smsSMS one-time password
hwkHardware WebAuthn/FIDO2 key
swkSoftware passkey (platform authenticator)
fedFederated identity (OIDC/social)
mfaMultiple distinct authentication factors used, including a user-verified passkey on its own

Token claims

After successful step-up, ID and access tokens include:

{
  "acr": "urn:wocha:aal2",
  "amr": ["pwd", "otp", "mfa"],
  "auth_time": 1705320000
}

  • Adaptive MFA — risk-based MFA policy and assessors
  • MFA API — programmatic TOTP enrolment
  • Passkeys — user-verified passkeys as MFA, AAL3 with hardware keys

On this page