Get a client
Tell us your redirect URI(s), for example https://your-app.example/callback. You get a client_id and a client_secret; your relying party details go into the registration certificate.
Developers · OpenID Connect · EU Wallet
To your application, the CodeB Relying Party Gateway is a standard OpenID Connect provider. You add one parameter and check one claim – the wallet protocols stay out of your code.
acr_values=urn:codeb:acr:eudi-wallet in the authorization request. 2. Accept only a validated id_token whose acr is urn:codeb:acr:eudi-wallet or urn:codeb:acr:eudi-wallet-linked.Authorization code + PKCE (S256) · acr / amr · RS256 id_token · JWKS · RP-initiated logout
Closed beta, in testing. Not certified.
https://phone.aloaha.com (discovery at /.well-known/openid-configuration)state, nonceacr_values=urn:codeb:acr:eudi-wallet (eID card: urn:codeb:acr:eidcard)acr in the id_token; amr contains vc (wallet) or sc + pop (eID card)openid, profile, email (also phone, address)client_secret_basic, client_secret_post, or a public client with PKCETell us your redirect URI(s), for example https://your-app.example/callback. You get a client_id and a client_secret; your relying party details go into the registration certificate.
Start an authorization code flow with PKCE (S256), state and nonce, and add acr_values=urn:codeb:acr:eudi-wallet.
On your server, swap the code for tokens at the token endpoint and validate the id_token: signature, issuer, audience, expiry, nonce.
Accept the sign-in only if acr is urn:codeb:acr:eudi-wallet or urn:codeb:acr:eudi-wallet-linked. Done.
Compact samples; complete, tested apps for six stacks (including Java and Go) are in the integration cookbook. Replace YOUR_CLIENT_ID, your-app.example and so on with your values.
All endpoints and keys come from one document; every OpenID Connect library reads it.
shell# Every endpoint comes from the discovery document.
curl -s https://phone.aloaha.com/.well-known/openid-configuration
# The parts you need:
# authorization_endpoint https://phone.aloaha.com/oauth2/v1/authorize
# token_endpoint https://phone.aloaha.com/oauth2/v1/token
# userinfo_endpoint https://phone.aloaha.com/oauth2/v1/userinfo
# end_session_endpoint https://phone.aloaha.com/oauth2/v1/logout
# jwks_uri (keys to check the id_token signature)
# code_challenge_methods_supported ["S256"]
# acr_values_supported ... urn:codeb:acr:eudi-wallet, urn:codeb:acr:eudi-wallet-linked, urn:codeb:acr:eidcardA plain link to your own server. For the official look, use the button and QR kit.
html<!-- The button only links to YOUR server; your server starts the sign-in. -->
<a href="/login" class="eudi-signin">Sign in with your EU Digital Identity Wallet</a>
<style>
.eudi-signin { display:inline-block; padding:.75rem 1.2rem; border-radius:10px;
background:#003399; color:#fff; font:600 1rem system-ui, sans-serif;
text-decoration:none; }
.eudi-signin:focus-visible { outline:3px solid #ffcc00; outline-offset:2px; }
</style>Useful to understand the flow, or for stacks without an OpenID Connect library.
http + shell# 1. Send the browser to the authorization endpoint (one line, URL-encoded):
https://phone.aloaha.com/oauth2/v1/authorize
?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https%3A%2F%2Fyour-app.example%2Fcallback
&scope=openid%20profile%20email
&acr_values=urn%3Acodeb%3Aacr%3Aeudi-wallet # straight to the wallet sign-in
&state=RANDOM_STATE
&nonce=RANDOM_NONCE
&code_challenge=BASE64URL_SHA256_OF_VERIFIER
&code_challenge_method=S256
# 2. The browser comes back to https://your-app.example/callback?code=...&state=...
# Check state, then exchange the code on your server:
curl -s https://phone.aloaha.com/oauth2/v1/token \
-u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
-d grant_type=authorization_code \
-d code=THE_CODE \
-d redirect_uri=https://your-app.example/callback \
-d code_verifier=THE_PKCE_VERIFIER
# 3. Validate the id_token (signature via jwks_uri, iss, aud, exp, nonce) and
# accept the sign-in only if acr is urn:codeb:acr:eudi-wallet
# or urn:codeb:acr:eudi-wallet-linked.// npm install express express-session openid-client@5
const express = require('express');
const session = require('express-session');
const { Issuer, generators } = require('openid-client');
const ISSUER = 'https://phone.aloaha.com';
const BASE = process.env.APP_BASE_URL; // e.g. https://your-app.example
const WALLET_ACRS = ['urn:codeb:acr:eudi-wallet', 'urn:codeb:acr:eudi-wallet-linked'];
const app = express();
app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false }));
let client;
async function codeb() {
if (!client) {
const issuer = await Issuer.discover(ISSUER);
client = new issuer.Client({ client_id: process.env.CODEB_CLIENT_ID,
client_secret: process.env.CODEB_CLIENT_SECRET, redirect_uris: [BASE + '/callback'], response_types: ['code'] });
}
return client;
}
app.get('/login', async (req, res, next) => {
try {
const c = await codeb(), verifier = generators.codeVerifier();
req.session.oidc = { verifier, state: generators.state(), nonce: generators.nonce() };
res.redirect(c.authorizationUrl({ scope: 'openid profile email', acr_values: WALLET_ACRS[0],
code_challenge: generators.codeChallenge(verifier), code_challenge_method: 'S256',
state: req.session.oidc.state, nonce: req.session.oidc.nonce }));
} catch (e) { next(e); }
});
app.get('/callback', async (req, res, next) => {
try {
const c = await codeb(), o = req.session.oidc || {};
const tokens = await c.callback(BASE + '/callback', c.callbackParams(req),
{ code_verifier: o.verifier, state: o.state, nonce: o.nonce }); // checks signature, iss, aud, exp, nonce
const claims = tokens.claims();
if (!WALLET_ACRS.includes(claims.acr)) return res.status(403).send('EU Digital Identity Wallet sign-in required');
req.session.regenerate(err => {
if (err) return next(err);
req.session.user = { sub: claims.sub, given_name: claims.given_name, family_name: claims.family_name };
res.redirect('/');
});
} catch (e) { next(e); }
});
app.listen(3000);# pip install flask authlib requests
import os
from flask import Flask, redirect, session
from authlib.integrations.flask_client import OAuth
WALLET_ACRS = ("urn:codeb:acr:eudi-wallet", "urn:codeb:acr:eudi-wallet-linked")
BASE = os.environ["APP_BASE_URL"] # e.g. https://your-app.example
app = Flask(__name__)
app.secret_key = os.environ["SESSION_SECRET"]
oauth = OAuth(app)
oauth.register("codeb",
client_id=os.environ["CODEB_CLIENT_ID"], client_secret=os.environ["CODEB_CLIENT_SECRET"],
server_metadata_url="https://phone.aloaha.com/.well-known/openid-configuration",
client_kwargs={"scope": "openid profile email", "code_challenge_method": "S256"})
@app.route("/login")
def login():
return oauth.codeb.authorize_redirect(BASE + "/callback", acr_values=WALLET_ACRS[0])
@app.route("/callback")
def callback():
token = oauth.codeb.authorize_access_token() # checks state, nonce, signature, iss, aud, exp
claims = token["userinfo"]
if claims.get("acr") not in WALLET_ACRS:
return "EU Digital Identity Wallet sign-in required", 403
session.clear()
session["user"] = {"sub": claims["sub"], "given_name": claims.get("given_name")}
return redirect("/")<?php
// composer require jumbojett/openid-connect-php
require __DIR__ . '/vendor/autoload.php';
use Jumbojett\OpenIDConnectClient;
session_start();
$oidc = new OpenIDConnectClient('https://phone.aloaha.com',
getenv('CODEB_CLIENT_ID'), getenv('CODEB_CLIENT_SECRET'));
$oidc->setRedirectURL(getenv('APP_BASE_URL') . '/callback.php');
$oidc->addScope(['openid', 'profile', 'email']);
$oidc->setCodeChallengeMethod('S256');
$oidc->addAuthParam(['acr_values' => 'urn:codeb:acr:eudi-wallet']);
$oidc->authenticate(); // redirects to CodeB, and on the way back validates the id_token
$acr = $oidc->getVerifiedClaims('acr');
if (!in_array($acr, ['urn:codeb:acr:eudi-wallet', 'urn:codeb:acr:eudi-wallet-linked'], true)) {
http_response_code(403);
exit('EU Digital Identity Wallet sign-in required');
}
session_regenerate_id(true);
$_SESSION['user'] = ['sub' => $oidc->getVerifiedClaims('sub'),
'given_name' => $oidc->getVerifiedClaims('given_name')];
header('Location: /');// ASP.NET Core 8: dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
var walletAcrs = new[] { "urn:codeb:acr:eudi-wallet", "urn:codeb:acr:eudi-wallet-linked" };
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(o =>
{
o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(o =>
{
o.Authority = "https://phone.aloaha.com";
o.ClientId = builder.Configuration["CodeB:ClientId"];
o.ClientSecret = builder.Configuration["CodeB:ClientSecret"];
o.ResponseType = "code";
o.UsePkce = true;
o.CallbackPath = "/callback";
o.MapInboundClaims = false; // keep "acr", "sub", "given_name" as they are
o.Scope.Clear(); o.Scope.Add("openid"); o.Scope.Add("profile"); o.Scope.Add("email");
o.Events.OnRedirectToIdentityProvider = ctx =>
{
ctx.ProtocolMessage.AcrValues = walletAcrs[0]; // straight to the wallet sign-in
return Task.CompletedTask;
};
o.Events.OnTokenValidated = ctx =>
{
var acr = ctx.Principal?.FindFirst("acr")?.Value;
if (acr is null || !walletAcrs.Contains(acr)) ctx.Fail("EU Digital Identity Wallet sign-in required");
return Task.CompletedTask;
};
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/", (HttpContext h) => $"Hello {h.User.FindFirst("given_name")?.Value}").RequireAuthorization();
app.Run();Decoded id_token after a wallet sign-in (illustrative values). Which personal attributes you get depends on the scopes and on what the site operator allows for your client.
json{
"iss": "https://phone.aloaha.com",
"aud": "YOUR_CLIENT_ID",
"sub": "eu_3f8a1d7e", // stable per person for your client's tenant
"acr": "urn:codeb:acr:eudi-wallet", // HOW the person signed in: check it
"amr": ["vc"], // vc = verifiable credential from the wallet
"auth_time": 1792490000,
"nonce": "RANDOM_NONCE",
"given_name": "Erika", // with scope profile, from the presented PID
"family_name": "Mustermann",
"birthdate": "1964-08-12",
"email": "erika@example.com" // with scope email, when the person has one
}# Same client, same code - only the acr changes:
acr_values=urn:codeb:acr:eidcard
# and accept the sign-in only if the id_token says
# "acr": "urn:codeb:acr:eidcard" (amr contains "sc" and "pop")
#
# The user signs in with the authentication key of a national eID card,
# read over NFC by the CodeB Wallet app.# After clearing your own session, sign the person out at CodeB too
# (RP-initiated logout); the return URL must be registered for your client:
https://phone.aloaha.com/oauth2/v1/logout
?client_id=YOUR_CLIENT_ID
&post_logout_redirect_uri=https%3A%2F%2Fyour-app.example%2FThe endpoint behind the “request deletion” link that wallets show for a relying party.
shell# A person (or you, as data controller) asks CodeB to delete the wallet sign-in
# records about them. Public endpoint, no sign-in; reviewed by an operator.
curl -s -X POST 'https://phone.aloaha.com/oidc.ashx?action=data-deletion-request' \
-H 'Content-Type: application/json' \
-d '{
"sub": "eu_3f8a1d7e",
"requester_type": "controller",
"contact": "privacy@your-app.example",
"reason": "Customer closed the account"
}'
# -> 202 { "request_id": "...", "status": "queued", "eta_days": 30 }Any widely used OpenID Connect client works: openid-client (Node.js), Authlib (Python), jumbojett/openid-connect-php, Microsoft.AspNetCore.Authentication.OpenIdConnect, Spring Security, go-oidc. Nothing CodeB-specific is needed.
Because a browser can be sent back to your callback from any sign-in. acr_values asks for the wallet sign-in; the acr claim in the validated id_token proves it happened (OpenID Connect Core 3.1.2.1). Every sample checks it.
Yes, as a public client with PKCE (no client secret), see the OIDC sign-in cookbook. A back end that keeps a client secret is the safer choice for production.
The person presented their wallet and that presentation was matched to an existing account on the site; otherwise the acr is urn:codeb:acr:eudi-wallet. Treat both as a wallet sign-in.
Use the CodeB Wallet or the EU reference wallet with test credentials, and the verifier self-test to see which HAIP checks a wallet passes.
Yes: we register test clients on our test tenant first. Ask us for a test client together with your redirect URI.
Send us your redirect URI and the name of your service; you get a client ID and secret on the test tenant.