Skip to main content

Overview

Openlane Console implements a robust, multi-layered authentication system using NextAuth.js with support for traditional credentials, OAuth providers, enterprise SSO, two-factor authentication, and modern WebAuthn passkeys.
All authentication flows are implemented in the (auth) route group and managed through NextAuth.js v5 (beta).

Authentication Methods

The Console supports multiple authentication methods that can be used individually or in combination:

Password-Based Login

Traditional email and password authentication with reCAPTCHA protection.
1

Enter Email

User enters their email address. The system automatically checks for SSO requirements via WebFinger API.
2

Email Validation

Email is validated against allowed domains and checked for SSO enforcement status.
3

Password Entry

If SSO is not enforced (or user is org owner), password field is displayed.
4

reCAPTCHA Verification

const recaptchaToken = await grecaptcha.execute(recaptchaSiteKey, { 
  action: 'login' 
})
Invisible reCAPTCHA validates the request before submission.
5

Credential Validation

NextAuth validates credentials against the backend API and establishes session.

OAuth Providers

Seamless authentication with GitHub and Google OAuth.
const github = async () => {
  setDirectOAuthCookie()
  await signIn('github', {
    redirectTo: redirectUrl,
  })
}
Required Environment Variables:
AUTH_GITHUB_ID=your_github_client_id
AUTH_GITHUB_SECRET=your_github_client_secret
AUTH_GOOGLE_ID=your_google_client_id
AUTH_GOOGLE_SECRET=your_google_client_secret
OAuth providers require proper callback URL configuration in the provider’s developer console. The callback URL format is: https://your-domain.com/api/auth/callback/[provider]

Enterprise SSO (OIDC)

Enterprise-grade Single Sign-On using OpenID Connect.
1

Configure Identity Provider

Organization admins navigate to Organization Settings → Authentication → SSO ConfigurationRequired fields:
  • Identity Provider (e.g., Okta, Azure AD, Google Workspace)
  • Client ID
  • Client Secret
  • OIDC Discovery Endpoint (e.g., https://your-idp.com/.well-known/openid-configuration)
2

Test SSO Connection

Before enforcing SSO, admins should verify the connection:
// Test flow stores flag in localStorage
localStorage.setItem('testing_sso', 'true')

const response = await fetch('/api/auth/sso', {
  method: 'POST',
  body: JSON.stringify({
    organization_id: currentOrgId,
    is_test: true,
  }),
})
Successful test redirects back with ?ssotested=1
3

Enable SSO Enforcement

Once tested, toggle Enforce SSO to require all organization members to use SSO.
Organization owners can still use password login even when SSO is enforced, providing a safety mechanism.
4

User Login with SSO

When a user enters their email:
// WebFinger API checks SSO configuration
const response = await fetch(
  `/api/auth/webfinger?email=${encodeURIComponent(email)}`
)

// Response indicates SSO requirements
{
  success: true,
  enforced: true,
  provider: "OKTA",
  organization_id: "org_123",
  is_org_owner: false
}
If SSO is enforced, the login form shows Continue with SSO button instead of password field.
SSO Callback Flow:
// apps/console/src/app/(auth)/login/sso/page.tsx
const handleSSOCallback = async () => {
  const code = searchParams?.get('code')
  const state = searchParams?.get('state')
  const organizationId = getCookie('organization_id')
  
  const response = await fetch('/api/auth/sso/callback', {
    method: 'POST',
    body: JSON.stringify({ code, state, organization_id: organizationId }),
  })
  
  const data = await response.json()
  
  if (data.success) {
    await signIn('credentials', {
      accessToken: data.access_token,
      refreshToken: data.refresh_token,
      session: data.session,
      type: 'oidc',
    })
  }
}

WebAuthn Passkeys

Modern passwordless authentication using biometrics, security keys, or device authentication.
Users can register passkeys from User Settings → Passkeys and security keys:
// Get registration options from server
const options = await getPasskeyRegOptions({
  email: userData?.user.email
})

// Store session cookie
setSessionCookie(options.session)

// Trigger browser WebAuthn API
const attestationResponse = await startRegistration({
  useAutoRegister: false,  // Disable conditional UI for settings
  optionsJSON: options.publicKey,
})

// Verify registration with server
const verificationResult = await verifyRegistration({
  attestationResponse
})
The Console uses SimpleWebAuthn library for WebAuthn implementation:
  • @simplewebauthn/browser - Client-side WebAuthn operations
  • @simplewebauthn/server - Server-side verification
Common WebAuthn errors:
  • AbortError / NotAllowedError: User cancelled the operation
  • InvalidStateError: Authenticator already registered for this device
  • NotSupportedError: Browser doesn’t support WebAuthn

Two-Factor Authentication (2FA)

TOTP-based two-factor authentication with recovery codes.
1

User Enables 2FA

From user settings, users scan a QR code with an authenticator app (Google Authenticator, Authy, 1Password, etc.)
2

Login Redirect

After successful password authentication, users with 2FA enabled are redirected to /tfa
3

Enter TOTP Code

// 6-digit authenticator code
<InputOTP maxLength={6} onChange={handleOtpChange}>
  {Array.from({ length: 6 }).map((_, index) => (
    <InputOTPSlot key={index} index={index} />
  ))}
</InputOTP>
Code is verified server-side:
const response = await secureFetch('/api/verifyOTP', {
  method: 'POST',
  body: JSON.stringify({ totp_code: otp }),
})
4

Recovery Codes

Users can also use 8-character recovery codes if they lose access to their authenticator:
// Switch to recovery code (8 characters)
<InputOTP maxLength={8}>
  {Array.from({ length: 8 }).map((_, index) => (
    <InputOTPSlot key={index} index={index} />
  ))}
</InputOTP>
Recovery codes are single-use. Once a recovery code is used, it cannot be used again.

Session Management

Session Configuration

Customize session behavior via environment variables:
# Cookie name (default: next-auth.session-token)
SESSION_COOKIE_NAME=openlane-session

# Cookie domain for subdomain sharing
SESSION_COOKIE_DOMAIN=.yourdomain.com

# Session duration in seconds (default: 30 days)
SESSION_NEXAUTH_MAX_AGE=2592000

Session Updates

The Console uses NextAuth’s update() function to refresh session data:
const { data: sessionData, update: updateSession } = useSession()

// Update session after organization switch
await updateSession({
  ...response.session,
  user: {
    ...sessionData.user,
    accessToken: response.access_token,
    organization: newOrgId,
    refreshToken: response.refresh_token,
  },
})

Domain Restrictions

Restrict signups and logins to specific email domains:
# Comma-separated list of allowed domains
NEXT_PUBLIC_ALLOWED_LOGIN_DOMAINS=company.com,partner.com
Organization admins can also configure allowed domains in Organization Settings → Authentication → Allowed Domains.

Security Best Practices

NextAuth requires HTTPS in production for secure cookie transmission. Set up SSL/TLS certificates for your domain.
Rotate OAuth client secrets, API tokens, and session secrets periodically:
  • OAuth credentials: Every 90 days
  • Session secrets: Every 6 months
  • API tokens: Based on your security policy
Protect against bot attacks by enabling reCAPTCHA:
RECAPTCHA_SECRET_KEY=your_recaptcha_v3_secret
Add the reCAPTCHA script to your site:
<script src="https://www.google.com/recaptcha/api.js?render=YOUR_SITE_KEY"></script>
Implement rate limiting and monitoring for failed authentication attempts. The Console supports integration with logging services via GCS_LOG_BUCKET.
Require 2FA or passkeys for users with elevated permissions (organization owners, compliance admins).

Troubleshooting

Problem: Users see “SSO callback failed” after attempting SSO loginSolutions:
  1. Verify OIDC Discovery Endpoint is accessible
  2. Check Client ID and Client Secret are correct
  3. Ensure callback URL is whitelisted in IdP: https://your-domain.com/api/auth/sso/callback
  4. Verify organization ID cookie is set properly
  5. Test SSO connection using the “Verify SSO connection” button
Problem: Users cannot register passkeys (InvalidStateError)Solutions:
  1. User may have already registered a passkey on this device - check existing passkeys
  2. Clear browser data and try again
  3. Use a different device or authenticator
  4. Ensure useAutoRegister: false in settings flows
Problem: TOTP codes consistently fail validationSolutions:
  1. Check device time is synchronized (TOTP is time-based)
  2. Ensure user is entering the current code (codes expire every 30 seconds)
  3. Verify the correct secret was scanned during setup
  4. Use a recovery code as fallback

Next Steps

Dashboard

Learn about the dashboard and user workflows

Organizations

Set up multi-organization access

Deployment

Configure authentication in production

API Reference

Explore authentication APIs

Build docs developers (and LLMs) love