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
- Your page calls the OIDC create endpoint.
- UNQVerify returns a redirect URL as plain text.
- Your page sends the browser to that URL.
- The user completes MitID verification.
- Your callback page receives a
jwtquery parameter. - 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-keyheader - The response is plain text, not JSON
RedirectUrishould 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
| Claim | Meaning |
|---|---|
iss | The issuer. Used to choose the correct JWKS endpoint |
exp | Expiry time as a Unix timestamp |
aldersverificeringdk_verification_result | true if the user met the age requirement |
aldersverificeringdk_verification_age | The verified age returned by MitID verification |
Recommended behavior after success
- 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
jwtquery parameter without verifying its signature - Forgetting to remove
jwtfrom the URL after processing