From eec87f678da8a48051f809a8788f5d462ed66a0e Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Sun, 15 Dec 2024 01:26:24 +0100 Subject: [PATCH] implemented implicit flow, added user claim to id token Signed-off-by: Stephan Richter --- .../de/srsoftware/oidc/app/Application.java | 5 +-- .../oidc/backend/ClientController.java | 33 +++++++++++++++---- .../oidc/backend/TokenController.java | 3 +- .../resources/en/scripts/authorization.js | 5 +-- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java b/de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java index 9917a41..7462ae0 100644 --- a/de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java +++ b/de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java @@ -79,8 +79,9 @@ public class Application { new WellKnownController().bindPath(WELL_KNOWN, "/realms/oidc" + WELL_KNOWN).on(server); new UserController(mailConfig, sessionService, userService, staticPages).bindPath(API_USER).on(server); var tokenControllerConfig = new TokenController.Configuration(10); - new TokenController(authService, clientService, keyManager, userService, tokenControllerConfig).bindPath(API_TOKEN).on(server); - new ClientController(authService, clientService, sessionService, userService).bindPath(API_CLIENT).on(server); + var tokenController = new TokenController(authService, clientService, keyManager, userService, tokenControllerConfig); + tokenController.bindPath(API_TOKEN).on(server); + new ClientController(authService, clientService, sessionService, userService, tokenController).bindPath(API_CLIENT).on(server); new KeyStoreController(keyStore).bindPath(JWKS).on(server); new EmailController(mailConfig, sessionService, userService).bindPath(API_EMAIL).on(server); server.setExecutor(Executors.newCachedThreadPool()); diff --git a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java index a38bcf1..97e1315 100644 --- a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java +++ b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java @@ -25,12 +25,14 @@ public class ClientController extends Controller { private final AuthorizationService authorizations; private final ClientService clients; private final UserService users; + private final TokenController tokens; - public ClientController(AuthorizationService authorizationService, ClientService clientService, SessionService sessionService, UserService userService) { + public ClientController(AuthorizationService authorizationService, ClientService clientService, SessionService sessionService, UserService userService, TokenController tokenController) { super(sessionService); authorizations = authorizationService; clients = clientService; users = userService; + tokens = tokenController; } @@ -50,15 +52,18 @@ public class ClientController extends Controller { var scopes = toList(json, SCOPE); if (!scopes.contains(OPENID)) return badRequest(ex, Error.message(ERROR_MISSING_PARAMETER, PARAM, "Scope: openid", STATE, state)); var responseTypes = toList(json, RESPONSE_TYPE); + var types = 0; for (var responseType : responseTypes) { switch (responseType) { case CODE: + case ID_TOKEN: + types++; break; default: return badRequest(ex, Error.message(ERROR_UNSUPPORTED_RESPONSE_TYPE, RESPONSE_TYPE, responseType, STATE, state)); } } - if (!responseTypes.contains(CODE)) return badRequest(ex, Error.message(ERROR_MISSING_CODE_RESPONSE_TYPE, STATE, state)); + if (types < 1) return badRequest(ex, Error.message(ERROR_MISSING_CODE_RESPONSE_TYPE, STATE, state)); var client = optClient.get(); var redirect = json.getString(REDIRECT_URI); @@ -74,16 +79,32 @@ public class ClientController extends Controller { } if (json.has(NONCE)) authorizations.nonce(user.uuid(), client.id(), json.getString(NONCE)); - var authResult = authorizations.getAuthorization(user.uuid(), client.id(), scopes); if (!authResult.unauthorizedScopes().isEmpty()) { return sendContent(ex, Map.of("unauthorized_scopes", authResult.unauthorizedScopes(), "rp", client.name())); } + var joinedAuthorizedScopes = Optionals.nullable(authResult.authorizedScopes()).map(AuthorizedScopes::scopes).map(list -> String.join(" ", list)); - var result = new HashMap(); + + var result = new HashMap(); + joinedAuthorizedScopes.ifPresent(authorizedScopes -> result.put(SCOPE, authorizedScopes)); - result.put(CODE, authResult.authCode()); - if (state != null) result.put(STATE, state); + + if (responseTypes.contains(ID_TOKEN)) { + var accessToken = users.accessToken(user); + var issuer = hostname(ex); + String jwToken = tokens.createJWT(client, user, accessToken, issuer); + ex.getResponseHeaders().add("Cache-Control", "no-store"); + result.put(ACCESS_TOKEN, accessToken.id()); + result.put(TOKEN_TYPE, BEARER); + result.put(EXPIRES_IN, 3600); + result.put(ID_TOKEN, jwToken); + } else if (responseTypes.contains(CODE)) { + result.put(CODE, authResult.authCode()); + if (state != null) result.put(STATE, state); + } + + return sendContent(ex, result); } diff --git a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/TokenController.java b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/TokenController.java index 9b8b649..abd2056 100644 --- a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/TokenController.java +++ b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/TokenController.java @@ -127,7 +127,7 @@ public class TokenController extends PathHandler { return sendContent(ex, response); } - private String createJWT(Client client, User user, AccessToken accessToken, String issuer) { + String createJWT(Client client, User user, AccessToken accessToken, String issuer) { try { PublicJsonWebKey key = keyManager.getKey(); var algo = key.getAlgorithm(); @@ -181,6 +181,7 @@ public class TokenController extends PathHandler { claims.setClaim(AT_HASH, atHash); claims.setClaim(CLIENT_ID, client.id()); claims.setClaim(EMAIL, user.email()); // additional claims/attributes about the subject can be added + claims.setClaim(USER, user.username()); optNonce.ifPresent(nonce -> claims.setClaim(NONCE, nonce)); claims.setGeneratedJwtId(); // a unique identifier for the token diff --git a/de.srsoftware.oidc.web/src/main/resources/en/scripts/authorization.js b/de.srsoftware.oidc.web/src/main/resources/en/scripts/authorization.js index e565373..bfbd20d 100644 --- a/de.srsoftware.oidc.web/src/main/resources/en/scripts/authorization.js +++ b/de.srsoftware.oidc.web/src/main/resources/en/scripts/authorization.js @@ -35,8 +35,9 @@ function handleResponse(response){ return; } if (json.scope){ + var separator = json.id_token ? '#' : '?'; var query = Object.keys(json).map(key => `${key}=${encodeURIComponent(json[key])}`).join('&'); - var url = params.get('redirect_uri') + '?' + query.toString(); + var url = params.get('redirect_uri') + separator + query.toString(); redirect(url); return; } @@ -53,7 +54,7 @@ function handleResponse(response){ if (json.error) show(json.error); if (json.metadata.client_id) setText('client_id',json.metadata.client_id); if (json.metadata.parameter) setText('parameter',json.metadata.parameter); - if (json.metadata.redirect_uri) setText('redirect_uri',json.metadata.redirect_uri); + if (json.metadata.redirect_uri) setText('redirect_uri',json.metadata.redirect_uri); if (json.metadata.response_type)setText('response_type',json.metadata.response_type) }); /*if (json.error != "invalid_request_uri"){