PreferType + NonInteractive Integration
This guide demonstrates how to integrate applications using preferType to specify the preferred credential type and nonInteractive mode for streamlined authentication.
Overview
The preferType parameter allows you to:
- Specify preferred credential type (password, email, totp, webauthn, etc.)
- Skip credential selection UI
- Streamline user experience for specific use cases
Combined with nonInteractive mode, this creates fast, targeted authentication flows.
Use Cases
- Email-only verification: Mobile apps with email verification
- WebAuthn-first: Hardware key authentication for high-security apps
- TOTP-preferred: Apps requiring 2FA
- Password-only: Traditional username/password flows
Flow Diagram
sequenceDiagram
participant Client
participant UAAA
Client->>UAAA: 1. Redirect with preferType=email<br/>and nonInteractive=true
Note over UAAA: 2. Skip credential<br/>selection UI
Note over UAAA: 3. Show email<br/>verification form
UAAA->>Client: 4. User enters email code
UAAA->>Client: 5. Verify and redirect<br/>with auth code
Client->>UAAA: 6. Exchange code for tokens
UAAA->>Client: 7. Return access_token,<br/>id_token, refresh_tokenParameters
preferType
Specifies the preferred credential type:
preferType=<credential_type>Supported Types:
password: Username and passwordemail: Email OTPsms: SMS OTPtotp: Time-based OTP (Google Authenticator)webauthn: WebAuthn/FIDO2 hardware keys- Custom plugin types (e.g.,
iaaa)
nonInteractive
Skips UI when possible:
nonInteractive=trueBehavior:
- If user has the preferred credential type → use it directly
- If user doesn't have it → show minimal UI
- Reduces friction for known credential types
Parameter Encoding
For OAuth2 standard compliance, preferType, nonInteractive, and security_level should be base64-encoded in the scope parameter:
// 1. Create parameter object
const params = {
preferType: 'email',
nonInteractive: true,
security_level: '2'
}
// 2. Encode as base64
const encoded = Buffer.from(JSON.stringify(params)).toString('base64')
// In browser: btoa(JSON.stringify(params))
// 3. Include in scope parameter
const scope = `openid profile uaaa+param://${encoded}`
// 4. Use in authorization URL
const authUrl = `https://auth.example.com/oauth/authorize?` +
`client_id=myapp&` +
`response_type=code&` +
`redirect_uri=https://myapp.com/callback&` +
`scope=${encodeURIComponent(scope)}`Implementation Examples
Email Verification Flow
Perfect for email-based apps:
const express = require('express')
const axios = require('axios')
const config = {
uaaaIssuer: 'https://auth.example.com',
clientId: 'myapp.example.com',
redirectUri: 'https://myapp.example.com/auth/callback'
}
app.get('/auth/login-email', (req, res) => {
// Encode UAAA-specific parameters in scope
const uaaaParams = {
preferType: 'email',
nonInteractive: true,
security_level: '2' // Email is MEDIUM level (2)
}
const encodedParams = Buffer.from(JSON.stringify(uaaaParams)).toString('base64')
const params = new URLSearchParams({
client_id: config.clientId,
response_type: 'code',
redirect_uri: config.redirectUri,
scope: `openid email uperm://myapp.example.com/** uaaa+param://${encodedParams}`
})
res.redirect(`${config.uaaaIssuer}/oauth/authorize?${params}`)
})
app.get('/auth/callback', async (req, res) => {
const { code } = req.query
// Exchange code for tokens (same as standard OAuth2)
const tokenResponse = await axios.post(
`${config.uaaaIssuer}/oauth/token`,
new URLSearchParams({
grant_type: 'authorization_code',
code: code,
redirect_uri: config.redirectUri,
client_id: config.clientId
})
)
const { access_token, id_token } = tokenResponse.data
// Fetch user email
const userInfo = await axios.get(
`${config.uaaaIssuer}/oauth/userinfo`,
{ headers: { Authorization: `Bearer ${access_token}` } }
)
// User is now authenticated with verified email
req.session.user = userInfo.data
res.redirect('/dashboard')
})WebAuthn-First Flow
For high-security applications:
// src/auth/webauthn-login.ts
interface LoginOptions {
minSecurityLevel?: number
requiredClaims?: string[]
}
export async function loginWithWebAuthn(options: LoginOptions = {}) {
// Encode UAAA-specific parameters in scope
const uaaaParams = {
preferType: 'webauthn',
security_level: '3', // HIGH level
nonInteractive: true
}
const encodedParams = btoa(JSON.stringify(uaaaParams))
const params = new URLSearchParams({
client_id: 'myapp.example.com',
response_type: 'code',
redirect_uri: 'https://myapp.example.com/auth/callback',
scope: `openid profile uperm://myapp.example.com/** uaaa+param://${encodedParams}`
})
// Add optional claims
if (options.requiredClaims) {
params.append('claims', JSON.stringify({
userinfo: options.requiredClaims.reduce((acc, claim) => {
acc[claim] = { essential: true }
return acc
}, {} as Record<string, any>)
}))
}
window.location.href = `https://auth.example.com/oauth/authorize?${params}`
}
// Usage
loginWithWebAuthn({
minSecurityLevel: 3,
requiredClaims: ['realname', 'email']
})TOTP-Preferred Flow
For 2FA-enabled apps:
from flask import Flask, redirect, request, session
from urllib.parse import urlencode
app = Flask(__name__)
app.secret_key = 'your-secret'
CONFIG = {
'uaaa_issuer': 'https://auth.example.com',
'client_id': 'myapp.example.com',
'redirect_uri': 'https://myapp.example.com/auth/callback'
}
@app.route('/auth/login-2fa')
def login_2fa():
"""Login preferring TOTP (Google Authenticator)"""
import json
import base64
# Encode UAAA-specific parameters in scope
uaaa_params = {
'preferType': 'totp',
'security_level': '3', # HIGH level (totp is 3)
'nonInteractive': True
}
encoded_params = base64.b64encode(json.dumps(uaaa_params).encode()).decode()
params = {
'client_id': CONFIG['client_id'],
'response_type': 'code',
'redirect_uri': CONFIG['redirect_uri'],
'scope': f"openid profile uperm://myapp.example.com/** uaaa+param://{encoded_params}"
}
auth_url = f"{CONFIG['uaaa_issuer']}/oauth/authorize?{urlencode(params)}"
return redirect(auth_url)
@app.route('/auth/callback')
def callback():
code = request.args.get('code')
# Exchange code for tokens...
return 'Authenticated with TOTP!'Password-Only Flow
Traditional username/password:
// React example
import { useEffect } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'
export function LoginPage() {
const navigate = useNavigate()
const handleLogin = () => {
// Encode UAAA-specific parameters in scope
const uaaaParams = {
preferType: 'password',
nonInteractive: true,
security_level: '2' // MEDIUM level
}
const encodedParams = btoa(JSON.stringify(uaaaParams))
const params = new URLSearchParams({
client_id: 'myapp.example.com',
response_type: 'code',
redirect_uri: 'https://myapp.example.com/auth/callback',
scope: `openid profile email uperm://myapp.example.com/** uaaa+param://${encodedParams}`
})
window.location.href = `https://auth.example.com/oauth/authorize?${params}`
}
return (
<div>
<h1>Login</h1>
<button onClick={handleLogin}>
Sign in with Password
</button>
</div>
)
}
export function CallbackPage() {
const [searchParams] = useSearchParams()
const navigate = useNavigate()
useEffect(() => {
const code = searchParams.get('code')
if (code) {
exchangeCodeForTokens(code).then(() => {
navigate('/dashboard')
})
}
}, [searchParams, navigate])
return <div>Processing login...</div>
}Combining preferType with Other Parameters
With Specific User
// Encode UAAA-specific parameters in scope
const uaaaParams = {
preferType: 'email',
nonInteractive: true
}
const encodedParams = Buffer.from(JSON.stringify(uaaaParams)).toString('base64')
const params = {
client_id: 'myapp.example.com',
response_type: 'code',
redirect_uri: 'https://myapp.example.com/callback',
scope: `openid email uaaa+param://${encodedParams}`,
// Pre-fill username (standard OIDC parameter)
login_hint: 'user@example.com'
}With Required Permissions
// Encode UAAA-specific parameters in scope
const uaaaParams = {
preferType: 'totp',
security_level: '3' // Require HIGH level for write permissions
}
const encodedParams = Buffer.from(JSON.stringify(uaaaParams)).toString('base64')
const params = {
client_id: 'myapp.example.com',
response_type: 'code',
redirect_uri: 'https://myapp.example.com/callback',
// Request specific permissions
scope: `openid profile uperm://myapp.example.com/api/read uperm://myapp.example.com/api/write uaaa+param://${encodedParams}`
}With Security Level Upgrade
// User already logged in with LOW level, upgrade to HIGH
const uaaaParams = {
preferType: 'totp',
security_level: '3'
}
const encodedParams = Buffer.from(JSON.stringify(uaaaParams)).toString('base64')
const params = {
client_id: 'myapp.example.com',
response_type: 'code',
redirect_uri: 'https://myapp.example.com/callback',
scope: `uperm://myapp.example.com/admin/** upgradable uaaa+param://${encodedParams}`,
// Include existing session (standard OIDC parameters)
prompt: 'none', // Don't show login if already authenticated
max_age: '300' // Require recent authentication (5 min)
}User Experience Flows
Scenario 1: User Has Preferred Credential
1. User clicks "Login with Email"
2. Redirect to UAAA with preferType=email
3. UAAA detects user has email credential
4. Show email verification form directly (no credential selection)
5. User enters OTP code
6. Redirect back to app with tokensScenario 2: User Doesn't Have Preferred Credential
1. User clicks "Login with WebAuthn"
2. Redirect to UAAA with preferType=webauthn
3. UAAA detects user doesn't have WebAuthn
4. Show option to:
a. Register WebAuthn credential
b. Use alternative credential
5. User chooses and completes authentication
6. Redirect back to appScenario 3: New User with PreferType
1. New user clicks "Login with Email"
2. Redirect to UAAA with preferType=email
3. UAAA shows registration form with email prominent
4. User registers with email
5. Email verified immediately
6. Redirect back to app with tokensSecurity Levels by Credential Type
| Credential | Security Level | preferType Value |
|---|---|---|
| Email OTP | MEDIUM (2) | email |
| SMS OTP | HIGH (3) | sms |
| Password | MEDIUM (2) | password |
| TOTP | HIGH (3) | totp |
| WebAuthn | HIGH (3) | webauthn |
Error Handling
app.get('/auth/callback', async (req, res) => {
const { code, error, error_description } = req.query
if (error) {
// Handle authentication errors
switch (error) {
case 'access_denied':
// User cancelled or denied access
return res.redirect('/auth/cancelled')
case 'credential_unavailable':
// Preferred credential type not available
return res.redirect('/auth/login?fallback=true')
case 'security_level_insufficient':
// User's credential doesn't meet minSecurityLevel
return res.redirect('/auth/upgrade-required')
default:
console.error(`Auth error: ${error} - ${error_description}`)
return res.redirect('/auth/error')
}
}
// Continue with token exchange...
})Best Practices
Fallback Strategy: Always handle cases where preferred credential isn't available
Clear UI: Inform users about authentication method before redirecting
Security Level Matching: Match
security_levelwithpreferTypeProgressive Enhancement: Start with lower security level, upgrade when needed
User Choice: Provide alternative login methods
Testing
// Test different credential types
describe('PreferType Authentication', () => {
test('email verification flow', async () => {
const authUrl = buildAuthUrl({ preferType: 'email' })
// Simulate user flow...
})
test('fallback when credential unavailable', async () => {
const authUrl = buildAuthUrl({ preferType: 'webauthn' })
// Test fallback behavior...
})
test('security level requirement', async () => {
const authUrl = buildAuthUrl({
preferType: 'totp',
minSecurityLevel: 2
})
// Verify security level enforcement...
})
})Next Steps
- PreAuth Integration: Combine with external identity systems
- OAuth2 Integration: Standard OAuth2 flow
- Configuration: Configure credential types