Skip to content

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 ​

  1. Email-only verification: Mobile apps with email verification
  2. WebAuthn-first: Hardware key authentication for high-security apps
  3. TOTP-preferred: Apps requiring 2FA
  4. Password-only: Traditional username/password flows

Flow Diagram ​

mermaid
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_token

Parameters ​

preferType ​

Specifies the preferred credential type:

preferType=<credential_type>

Supported Types:

  • password: Username and password
  • email: Email OTP
  • sms: SMS OTP
  • totp: Time-based OTP (Google Authenticator)
  • webauthn: WebAuthn/FIDO2 hardware keys
  • Custom plugin types (e.g., iaaa)

nonInteractive ​

Skips UI when possible:

nonInteractive=true

Behavior:

  • 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:

javascript
// 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:

javascript
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:

typescript
// 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:

python
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:

javascript
// 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 ​

javascript
// 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: '[email protected]'
}

With Required Permissions ​

javascript
// 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 ​

javascript
// 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 tokens

Scenario 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 app

Scenario 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 tokens

Security Levels by Credential Type ​

CredentialSecurity LevelpreferType Value
Email OTPMEDIUM (2)email
SMS OTPHIGH (3)sms
PasswordMEDIUM (2)password
TOTPHIGH (3)totp
WebAuthnHIGH (3)webauthn

Error Handling ​

javascript
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 ​

  1. Fallback Strategy: Always handle cases where preferred credential isn't available

  2. Clear UI: Inform users about authentication method before redirecting

  3. Security Level Matching: Match security_level with preferType

  4. Progressive Enhancement: Start with lower security level, upgrade when needed

  5. User Choice: Provide alternative login methods

Testing ​

javascript
// 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 ​

Released under the MIT License.