@unqtech/age-verification-mitid
Version:
Frontend SDK for age verification via UNQVerify using MitID
257 lines (198 loc) • 8.18 kB
Markdown
# @unqtech/age-verification-mitid
Frontend SDK for age verification using UNQVerify (MitID-powered).
🔗 Official site: [aldersverificering.dk](https://www.aldersverificering.dk)
### Contents
- [Installation](#installation)
- [Quick start (npm)](#quick-start-npm)
- [Outcome codes](#outcome-codes)
- [`init(config)` options](#initconfig-options)
- [Other functions](#other-functions)
- [Backwards compatibility](#backwards-compatibility)
- [CDN / No-bundler usage](#cdn--no-bundler-usage)
- [WooCommerce integration](#woocommerce-integration)
- [Security](#security)
- [License](#license)
## Installation
```bash
npm install @unqtech/age-verification-mitid
```
TypeScript users can also import the types directly:
```ts
import type { UnqVerifyConfig, VerificationErrorCode, VerificationOutcome } from '@unqtech/age-verification-mitid'
```
## Quick start (npm)
```ts
import { init, startVerificationWithRedirect, isVerified } from '@unqtech/age-verification-mitid'
init({
publicKey: 'pk_test_abc123',
ageToVerify: 18,
redirectUri: 'https://yourapp.com/verify-result',
onVerified: (payload) => console.log('Verified!', payload),
onDenied: (outcome) => alert('You do not meet the age requirement.'),
onCancelled: (outcome) => console.info('Cancelled:', outcome.code),
onError: (outcome) => console.error('Error:', outcome.code, outcome.message),
})
if (!isVerified()) {
startVerificationWithRedirect()
// or: startVerificationWithPopup()
}
```
On the page your `redirectUri` points to:
```ts
import { handleRedirectResult } from '@unqtech/age-verification-mitid'
await handleRedirectResult({
onVerified: (payload) => { /* redirect user back */ },
onDenied: (outcome) => { /* show denied message */ },
onError: (outcome) => console.error(outcome.code, outcome.message),
targetOrigin: 'https://yourapp.com', // recommended in popup mode
})
```
## Outcome codes
Every non-success path provides a `VerificationOutcome` object:
```ts
interface VerificationOutcome {
code: VerificationErrorCode // stable, machine-readable
message: string
raw?: unknown
}
```
| Code | Callback | When |
|---|---|---|
| `UNDER_AGE` | `onDenied` | MitID returned `verification_result: false`. Covers both genuine under-age and user opting out at the MitID screen — MitID sends the same response for both. |
| `USER_CANCELLED` | `onCancelled` | User cancelled inside the MitID flow |
| `POPUP_CLOSED` | `onCancelled` | User closed the popup window manually |
| `POPUP_TIMEOUT` | `onCancelled` | Popup open > 5 minutes without result |
| `POPUP_BLOCKED` | `onError` | Browser blocked the popup |
| `NETWORK_ERROR` | `onError` | API call failed |
| `TOKEN_INVALID` | `onError` | JWT missing, expired, or invalid signature |
| `UNTRUSTED_ORIGIN` | `onError` | postMessage received from an untrusted origin |
| `UNKNOWN_ERROR` | `onError` | Unexpected error |
## `init(config)` options
| Option | Type | Required | Description |
|---|---|---|---|
| `publicKey` | `string` | yes | Your `pk_test_...` or `pk_live_...` key |
| `ageToVerify` | `number` | yes | Minimum age threshold |
| `redirectUri` | `string` | yes | HTTPS URL the user returns to after MitID |
| `onVerified` | `(payload) => void` | yes | Called on successful verification |
| `onDenied` | `(outcome) => void` | no | Age requirement not met |
| `onCancelled` | `(outcome) => void` | no | Popup closed or timed out |
| `onError` | `(outcome) => void` | no | Technical/system error |
| `onFailure` | `(error?) => void` | no | Legacy catch-all (always invoked alongside granular callbacks) |
| `debug` | `boolean` | no | Verbose SDK logging (default `false`) |
## Other functions
| Function | Description |
|---|---|
| `startVerificationWithRedirect()` | Full-page redirect flow |
| `startVerificationWithPopup(popup?)` | Popup flow |
| `handleRedirectResult(opts)` | Process result on the redirect page |
| `isVerified()` | `true` if user has a valid, non-expired token |
| `getVerifiedAge()` | Verified age as `number`, or `null` |
| `resetVerification()` | Clears the session cookie |
## Backwards compatibility
`onFailure` is always called alongside any granular callback, so existing `v0.3` integrations continue to work unchanged.
```ts
// v0.3 — still works
onFailure: (err) => showError(err)
// v0.4 — use granular callbacks instead
onDenied: (outcome) => showMessage('Come back when you are older'),
onCancelled: (outcome) => hideModal(),
onError: (outcome) => reportError(outcome.code, outcome.raw),
```
## CDN / No-bundler usage
Load the self-contained UMD build directly — no npm or build step required.
```html
<script src="https://unpkg.com/@unqtech/age-verification-mitid@latest/dist/index.umd.js"></script>
```
All functions are available under `window.UnqVerify` after the script loads.
## WooCommerce integration
### 1 — Enqueue the script (`functions.php`)
```php
add_action( 'wp_enqueue_scripts', function () {
if ( is_shop() || is_product() || is_product_category() || is_cart() || is_checkout() ) {
wp_enqueue_script(
'unqverify-sdk',
'https://unpkg.com/@unqtech/age-verification-mitid@latest/dist/index.umd.js',
[], null, true
);
}
} );
```
### 2 — Initialize and gate the page
```php
add_action( 'wp_footer', function () {
if ( ! ( is_shop() || is_product() || is_product_category() || is_cart() || is_checkout() ) ) {
return;
}
?>
<script>
(function () {
var PUBLIC_KEY = 'pk_live_YOUR_KEY';
var REDIRECT_URI = '<?php echo esc_js( home_url( '/aldersverificering/' ) ); ?>';
if ( window.location.pathname.indexOf( '/aldersverificering' ) !== -1 ) {
UnqVerify.init({
publicKey: PUBLIC_KEY,
ageToVerify: 18,
redirectUri: REDIRECT_URI,
onVerified: function () {
var returnTo = sessionStorage.getItem( 'unqverify_return' ) || '<?php echo esc_js( wc_get_page_permalink( "shop" ) ); ?>';
window.location.href = returnTo;
},
onDenied: function () {
alert( 'Du opfylder desværre ikke alderskravet.' );
},
onError: function () {
alert( 'Aldersverificering mislykkedes. Prøv venligst igen.' );
}
});
UnqVerify.handleRedirectResult({
onVerified: function () {},
onDenied: function ( outcome ) { console.warn( outcome.code ); },
onError: function ( outcome ) { console.error( outcome.code ); }
});
return;
}
UnqVerify.init({
publicKey: PUBLIC_KEY,
ageToVerify: 18,
redirectUri: REDIRECT_URI,
onVerified: function () {},
onError: function () {}
});
if ( ! UnqVerify.isVerified() ) {
sessionStorage.setItem( 'unqverify_return', window.location.href );
UnqVerify.startVerificationWithRedirect();
}
})();
</script>
<?php
} );
```
### 3 — Create the verification result page
1. Go to **Pages → Add New** in WordPress admin
2. Set the slug to `aldersverificering` (matching `REDIRECT_URI` above)
3. Leave the content empty — the script handles everything
4. Register the URL as an **Allowed redirect URI** in your UNQVerify dashboard
## Security
- All JWTs are RS256-verified against the issuer's JWKS endpoint
- Unknown issuers are always rejected
- `postMessage` events are validated against a strict origin allowlist
- The `unqverify_token` cookie is set with `SameSite=Lax; Secure`
- Cookie lifetime is the greater of the JWT `exp` claim or **1 hour**, ensuring the verification session is not lost too quickly even if the token has a short TTL
JWKS endpoints:
```
https://test.api.aldersverificering.dk/well-known/openid-configuration/jwks (test)
https://api.aldersverificering.dk/well-known/openid-configuration/jwks (live)
```
## License
MIT © UNQTech ApS