In this article
July 2, 2026
July 2, 2026

Step-up authentication: Re-verify users before high-risk operations

AuthKit can now require a user to re-authenticate before a sensitive action without ending their session.

Explore with AI
Open in ChatGPT
Open in Claude
Open in Perplexity

Many auth systems treat every action in a session equally. Once a user is in, they are in. But not every action carries the same risk, and not every session should carry the same trust all the way through. 

Today, AuthKit adds step-up authentication: gate a specific operation behind a fresh verification, keep the session intact, and know exactly when the user last proved it was them.

The problem with authenticating only once

Once a user signs into AuthKit today, every action in that session carries the same level of trust. Reading a dashboard and deleting an account are authenticated identically. There is no way for a developer to say "this next action is sensitive, make the user prove it's still them" without either logging them out entirely or building something bespoke.

The actions that need this are easy to recognize: accessing an admin panel, changing billing details, revoking API keys, viewing secrets, or triggering any operation that is hard to undo. These are moments where the identity behind the session should be re-confirmed, not assumed.

Sessions also get long. A user who authenticated eight hours ago and stepped away from their laptop is not the same as a user who just proved who they are. The authentication event happened, but the freshness guarantee is gone. For applications under SOC 2, HIPAA, or PCI-DSS, re-verifying before sensitive data access is not just good practice, it is expected.

What we are shipping

Step-up authentication lets you gate a specific action behind a fresh verification without touching the user's existing session. The user proves it's still them, the session continues, and you get a signal that re-authentication happened.

Here is what lands with this release:

  • auth_time claim on access and ID tokens. Every AuthKit token now carries the Unix timestamp of the user's last active authentication (auth_time). Refresh tokens do not bump auth_time. Only a real interactive authentication does.
  • max_age on /user_management/authorize. Pass max_age with a number of seconds to force re-authentication if the user's last auth is older than that threshold. Pass max_age=0 to always force it. Your app can check auth_time from the token client-side as an optimization, but the enforcement happens server-side at /authorize.
  • A hosted re-auth flow in AuthKit. When a step-up is triggered, users are challenged using the same method they originally authenticated with. Password users see a re-entry screen. Magic auth users receive a new code. Social and SSO users are sent back through their provider.
  • An authentication.reauthenticated event. Emitted on a completed step-up, with the user ID, session ID, method used, and new auth_time in the payload. This flows through the existing event pipeline.
  • SDK support. The WorkOS Node core SDK gets helpers to make this easy to integrate: a way to build the authorization URL with max_age, and a higher-level guard that reads auth_time from the access token and returns an error if the session is stale relative to your threshold. Other SDKs, like authkit-nextjs and authkit-tanstack-start, will follow soon.

How it works

The flow for a step-up looks like this:

  1. User takes an action in your app that you consider sensitive.
  2. Your server reads auth_time from the access token and compares it against your threshold. If it is stale, you redirect to /user_management/authorize with max_age set to your required freshness window.
  3. AuthKit detects that the existing session does not meet the freshness requirement and presents a re-authentication challenge.
  4. The user completes the challenge. AuthKit issues fresh access and ID tokens with an updated auth_time, rotates the refresh token, and redirects back to your app.
  5. The session ID (sid) is unchanged. The authentication.reauthenticated event fires.
  6. Your app receives tokens with the new auth_time, confirms they are fresh, and lets the action proceed.

A practical example using max_age:

  
// Before a destructive action, redirect to re-auth if the session
// is more than 5 minutes old.
const authorizationUrl = workos.getAuthorizationUrl({
  redirectUri: 'https://your-app.com/callback',
  maxAge: 300, // 5 minutes in seconds
});

return redirect(authorizationUrl);
  

And for reading the claims back:

  
// In your callback or middleware, inspect the access token.
const { authTime } = parseAccessToken(accessToken);
const isRecentEnough = Date.now() / 1000 - authTime < 300;
  

Try it today

Step-up authentication is available now. Start with the WorkOS Node SDK and the documentation. If you have feedback on the implementation or specific flows you are trying to support, we want to hear it.