OIDC IdP — Login con Credencial Verificable¶
El Verifier de EUDIStack actúa como un Identity Provider (IdP) OIDC estándar que integra credenciales verificables. En lugar de manejar usuarios y contraseñas, tus usuarios pueden autenticarse presentando una credencial verificable (SD-JWT VC) desde su wallet EUDI.
¿Cuándo usar esta guía?
- Tu aplicación ya soporta OIDC (Spring Security, Auth.js, Keycloak, NextAuth, etc.)
- Quieres reemplazar autenticación con usuario/contraseña por credenciales verificables (eIDAS 2.0)
- No quieres implementar OID4VP directamente en tu backend
Configuración Inicial¶
El Verifier expone un endpoint de configuración OpenID estándar. El dominio se configura por tenant:
Configura tu cliente OIDC para que use esta URL. El sistema deriva el issuer dinámicamente del dominio de la petición, lo que permite aislar tenants por subdominio sin configuración adicional.
PKCE con el método S256 es obligatorio para clientes públicos (SPAs, aplicaciones móviles). El método plain será rechazado.
- Usa siempre
code_challenge_method=S256 - Incluye
code_challengeen la petición de autorización - Envía el
code_verifieren el canje del token
Los clientes confidenciales que usan private_key_jwt (como Keycloak actuando como broker) no están sujetos a esta restricción.
El Flujo de Autenticación¶
El flujo es OIDC Authorization Code estándar desde el punto de vista de tu aplicación. Lo que cambia es la capa intermedia: en lugar de un formulario de usuario y contraseña, el Verifier orquesta una presentación de credencial con el wallet del usuario a través de OID4VP.
sequenceDiagram
autonumber
participant App as Tu Aplicación (OIDC Client)
participant Browser as Browser del Usuario
participant Portal as Portal (SPA Angular)
participant Verifier as EUDIStack Verifier
participant Wallet as EUDI Wallet
App->>Browser: Redirige a /verifier/oidc/authorize
Browser->>Verifier: Authorization Request (scope, PKCE)
Verifier-->>Browser: 302 al Portal con authRequest + state
Browser->>Portal: Carga página de login con QR
Portal->>Verifier: GET /api/login/events?state=... (SSE)
Note over Portal,Verifier: Conexión SSE abierta esperando resultado
Wallet->>Verifier: GET /authorization-request (escanea QR)
Wallet->>Verifier: POST /response (VP Token)
Note over Verifier: Valida credencial, firma y revocación
Verifier-->>Portal: SSE event: redirect URL con code
Portal-->>Browser: Redirige a redirect_uri?code=...&state=...
App->>Verifier: POST /verifier/oidc/token (code + PKCE verifier)
Verifier-->>App: access_token + id_token Tu aplicación redirige el browser del usuario al Authorization Endpoint indicando qué tipo de credencial necesitas mediante el parámetro scope. Siempre incluye openid:
GET /verifier/oidc/authorize?
response_type=code&
client_id=mi-app&
scope=openid learcredential.employee&
state=xyz789&
redirect_uri=https://mi-app.com/callback&
code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-c&
code_challenge_method=S256
El Verifier redirige al usuario al Portal (SPA Angular), que muestra el QR de login. Mientras tanto, el Portal abre una conexión SSE contra el Verifier para recibir notificación cuando el wallet complete la presentación.
El Verifier traduce el scope learcredential.employee en una consulta DCQL que se incluye en el JWT OID4VP que descarga el wallet al escanear el QR.
Una vez que el usuario escanea el QR y presenta la credencial desde su wallet, el Verifier notifica al Portal vía SSE con la URL de redirección que incluye el code. El Portal redirige el browser del usuario a tu redirect_uri. Tu backend recibe el code y debe canjearlo en /verifier/oidc/token:
POST /verifier/oidc/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
client_id=mi-app&
code=SplxlOBeZQQYbYS6WxSbIA&
redirect_uri=https://mi-app.com/callback&
code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
Recibirás un access_token y un id_token con los datos extraídos de la credencial verificable.
Contenido del id_token¶
Claims y transformación de datos del token de identidad
El Verifier extrae los datos de la credencial verificable y los convierte en claims OIDC estándar usando Schema Profiles: ficheros JSON que definen las reglas de transformación para cada tipo de credencial.
El Verifier puede extraer datos de la credencial de varias formas:
- Path directo: Extrae un campo específico
- Concatenación: Combina múltiples campos
- Constante: Añade valores fijos
- Objeto completo: Incluye ramas JSON enteras dentro de un claim
Si el usuario presenta una credencial learcredential.employee, el id_token tendrá este aspecto:
{
"iss": "https://empresa.eudistack.net/verifier",
"sub": "juan.garcia@empresa.com",
"aud": "mi-app",
"iat": 1715000000,
"exp": 1715000060,
"auth_time": 1715000000,
"acr": "http://eidas.europa.eu/LoA/substantial",
"given_name": "Juan",
"family_name": "García",
"email": "juan.garcia@empresa.com",
"name": "Juan García",
"mandator": {
"organization": "Empresa S.A.",
"organizationIdentifier": "VATES-12345678",
"country": "ES"
},
"credential_type": "learcredential.employee.w3c.4",
"vc_json": "{\"type\": [\"VerifiableCredential\", \"LEARCredentialEmployee\"], ...}"
}
El claim vc_json contiene la credencial completa en crudo, útil para auditoría o si necesitas acceder a campos que no están mapeados en el token.
Valores ACR y eIDAS 2.0: El claim
acrindica el nivel de garantía (LoA) con el que se autenticó el usuario. EUDIStack utiliza los URIs definidos en el Reglamento eIDAS 2.0:
Valor acrNivel de garantía http://eidas.europa.eu/LoA/lowBajo http://eidas.europa.eu/LoA/substantialSustancial (valor por defecto con EUDI Wallet) http://eidas.europa.eu/LoA/highAlto El nivel efectivo depende del tipo de credencial presentada y de la configuración del Verifier. No se emite el valor numérico
"0"en producción; ese valor indicaría ausencia de nivel de garantía y no es válido en contextos eIDAS.
Consideraciones Importantes¶
El flujo de login (desde que se muestra el QR hasta que el usuario confirma en su wallet) tiene un tiempo límite de 120 segundos por defecto. Si se supera, la conexión SSE expira y el usuario debe iniciar el flujo de nuevo.
Tanto el code de autorización como el JWT de la petición OID4VP (que descarga el wallet al escanear el QR) son de un único uso. Esto previene ataques de repetición si alguien intercepta la URL.
La comprobación de revocación ocurre en el momento en que el wallet presenta la credencial (POST /oid4vp/auth-response), antes de que se emita el código de autorización. Si la credencial está revocada, el flujo se corta en ese punto y el Portal recibe una notificación de error vía SSE. Tu aplicación nunca llega a recibir un code.