Multi-factor authentication
This guide shows you how to enable multi-factor authentication (MFA) for your Ory project.
- Ory Console
- Ory CLI
To enable MFA using the Console:
- Log in to your Ory Console
- Select your workspace and project
- Navigate to the Authentication tab
- Click on Two-factor auth in the sidebar
Configure One-Time Codes
In the two-factor auth settings, you can enable and configure One-Time Codes for multi-factor authentication:

Toggle the "Enable one-time code multi factor authentication" toggle to allow users to receive one-time codes for MFA.
To enable MFA using the CLI:
- Get your current identity configuration:
# List all available workspaces
ory list workspaces
# List all available projects
ory list projects --workspace <workspace-id>
# Get the configuration
ory get identity-config --project <project-id> --workspace <workspace-id> --format yaml > identity-config.yaml
- Edit the configuration file to enable One-Time Codes and set MFA requirements:
# Enable One-Time Codes for MFA
selfservice:
  methods:
    code:
      enabled: true # Enable the one-time code method
      mfa_enabled: true
- Update your configuration:
ory update identity-config --project <project-id> --workspace <workspace-id> --file identity-config.yaml
This configuration forces users to provide the highest authentication factor available to access their account settings. For example, users without a second factor configured can access settings after they sign in with their password, but users that have a second factor set up will need to complete the second factor challenge.
What users will see
When MFA is enabled, users will see a second authentication screen after logging in:

Check AAL
Authentication Authorization Level (AAL) is a concept that describes the strength of the authentication factor used to access a resource.
- aal1: Password or OIDC - used one factor
- aal2: Password or OIDC and one-time code - used two factors
To check the AAL of the current session, use the authenticator_assurance_level on the toSession method.
- Expressjs
- Next.js
- Go
const requireAuth = async (req, res, next) => {
  try {
    const session = await ory.toSession({ cookie: req.header("cookie") })
    if (session.authenticator_assurance_level === "aal2") {
      req.session = session
      next()
    } else {
      res.redirect(
        `${process.env.ORY_SDK_URL}/self-service/login/browser?aal=aal2`,
      )
    }
  } catch (error) {
    res.redirect(`${process.env.ORY_SDK_URL}/self-service/login/browser`)
  }
}
app.get("/", requireAuth, (req, res) => {
  res.json(req.session.identity.traits) // { email: 'newtestuser@gmail.com' }
})
import { NextRequest, NextResponse } from "next/server"
import ory from "@/lib/ory"
export async function middleware(request: NextRequest) {
  try {
    const session = await ory.toSession({
      cookie: request.headers.get("cookie") || "",
    })
    // If toSession() doesn't throw, the session is valid
    if (session.authenticator_assurance_level === "aal2") {
      return NextResponse.next()
    } else {
      return NextResponse.redirect(
        `${process.env.NEXT_PUBLIC_ORY_SDK_URL}/self-service/login/browser?aal=aal2`,
      )
    }
  } catch (error) {
    return NextResponse.redirect(
      `${process.env.ORY_SDK_URL}/self-service/login/browser`,
    )
  }
}
// Configure which routes to protect
export const config = {
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico|public).*)"],
}
package main
import (
	"context"
	"errors"
	"log"
	"net/http"
	ory "github.com/ory/client-go"
)
func (app *App) sessionMiddleware(next http.HandlerFunc) http.HandlerFunc {
	return func(writer http.ResponseWriter, request *http.Request) {
		log.Printf("Checking authentication status\n")
		// Pass cookies to Ory's ToSession endpoint
		cookies := request.Header.Get("Cookie")
		// Verify session with Ory
		session, _, err := app.ory.FrontendAPI.ToSession(request.Context()).Cookie(cookies).Execute()
		// Redirect to login if session doesn't exist or is inactive
		if err != nil || (err == nil && !*session.Active) {
			log.Printf("No active session, redirecting to login\n")
			// Redirect to the login page
			http.Redirect(writer, request, app.tunnelUrl+"/self-service/login/browser", http.StatusSeeOther)
			return
		}
		if *session.AuthenticatorAssuranceLevel != "aal2" {
			http.Redirect(writer, request, app.tunnelUrl+"/self-service/login/browser?aal=aal2", http.StatusSeeOther)
			return
		}
		// Add session to context for the handler
		ctx := withSession(request.Context(), session)
		next.ServeHTTP(writer, request.WithContext(ctx))
	}
}
func withSession(ctx context.Context, v *ory.Session) context.Context {
	return context.WithValue(ctx, "req.session", v)
}
func getSession(ctx context.Context) (*ory.Session, error) {
	session, ok := ctx.Value("req.session").(*ory.Session)
	if !ok || session == nil {
		return nil, errors.New("session not found in context")
	}
	return session, nil
}
// Dashboard page protected by middleware
mux.Handle("/", app.sessionMiddleware(app.dashboardHandler))
User flow
- The user enters their username/password or uses another primary authentication method
- They see the MFA challenge screen
- A one-time code is sent to their email
- After entering the valid code, they gain access to the application or protected settings