Gå til hovedindhold
Custom Integrations

Redirect Flow

Start age verification without the SDK by calling the browser-facing create endpoint and processing the returned JWT yourself.

Redirect Flow

Redirect flow is the simplest custom integration.

It is the manual equivalent of startVerificationWithRedirect() followed by handleRedirectResult().

Need the exact request, parameter, and response contract first? See API Reference.

Flow overview

  1. Your page calls the OIDC create endpoint.
  2. UNQVerify returns a redirect URL as plain text.
  3. Your page sends the browser to that URL.
  4. The user completes MitID verification.
  5. Your callback page receives a jwt query parameter.
  6. Your callback page verifies the JWT and stores the result.

1. Request a verification redirect URL

type CreateVerificationInput = {
  publicKey: string
  ageToVerify: number
  redirectUri: string
}

function getApiBase(publicKey: string): string {
  if (publicKey.startsWith('pk_test_')) {
    return 'https://test.api.aldersverificering.dk'
  }

  if (publicKey.startsWith('pk_live_')) {
    return 'https://api.aldersverificering.dk'
  }

  throw new Error('Public key must start with pk_test_ or pk_live_')
}

export async function createVerificationRedirect({
  publicKey,
  ageToVerify,
  redirectUri,
}: CreateVerificationInput): Promise<string> {
  const query = new URLSearchParams({
    RedirectUri: redirectUri,
    AgeToVerify: String(ageToVerify),
  })

  const response = await fetch(
    `${getApiBase(publicKey)}/api/v1/oidc/create?${query.toString()}`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-public-key': publicKey,
      },
    },
  )

  if (!response.ok) {
    const body = await response.text().catch(() => 'Unable to read error response')
    throw new Error(`API error: ${response.status} - ${body}`)
  }

  const redirectUrl = await response.text()

  if (!redirectUrl.startsWith('https://') && !redirectUrl.startsWith('http://localhost')) {
    throw new Error('Invalid redirect URL returned from API')
  }

  return redirectUrl
}

Important request details

  • The endpoint uses query parameters, not a JSON request body
  • The public key goes in the x-public-key header
  • The response is plain text, not JSON
  • RedirectUri should point to a real page in your app

2. Start the redirect flow from your page

import { createVerificationRedirect } from './unqverify-api'

const publicKey = 'pk_test_your_key'
const ageToVerify = 18
const redirectUri = 'https://shop.example.com/verify-result'

async function startAgeVerification(): Promise<void> {
  const redirectUrl = await createVerificationRedirect({
    publicKey,
    ageToVerify,
    redirectUri,
  })

  window.location.href = redirectUrl
}

Run this from a user action such as a button click.

3. Process the callback page

On your callback page, read jwt, verify it, remove it from the address bar, and store it.

The helper functions below come from Token Validation.

import {
  storeVerificationToken,
  validateVerificationToken,
} from './unqverify-token'

async function handleVerificationResult(): Promise<void> {
  const url = new URL(window.location.href)
  const token = url.searchParams.get('jwt')

  if (!token) {
    throw new Error('No JWT found in URL')
  }

  const result = await validateVerificationToken(token)

  if (!result.ok) {
    if (result.code === 'UNDER_AGE') {
      // Show a business denial state.
      return
    }

    throw new Error(result.message)
  }

  storeVerificationToken(token, result.payload.exp)

  url.searchParams.delete('jwt')
  window.history.replaceState({}, document.title, url.toString())

  window.location.href = '/checkout'
}

handleVerificationResult().catch((error) => {
  console.error('Verification failed', error)
})

Claims you should expect after verification

ClaimMeaning
issThe issuer. Used to choose the correct JWKS endpoint
expExpiry time as a Unix timestamp
aldersverificeringdk_verification_resulttrue if the user met the age requirement
aldersverificeringdk_verification_ageThe verified age returned by MitID verification
  • Store the token in a secure cookie or session
  • Use the verified age to decide whether stricter age gates still apply
  • Re-check the token before allowing checkout, order placement, or other restricted actions

The example above stores the token in browser-managed state for simplicity. That is acceptable for client-side UX, but it is not a complete trust boundary. If the site has XSS, a JavaScript-readable token can be stolen. Final authorization decisions should rely on server-side validation or a server-issued session derived from the verified JWT.

Common mistakes

  • Using a test key with the production API base, or the reverse
  • Expecting a JSON response from /api/v1/oidc/create
  • Trusting the jwt query parameter without verifying its signature
  • Forgetting to remove jwt from the URL after processing

Next step

Add JWT verification and storage helpers

On this page