Protocol & Login Endpoints
RealmsResource is the single JAX-RS entry point under /realms/{realm}/... — every sub-resource locator resolves the realm from the URL first, then hands off to a protocol endpoint (OIDC/SAML), the authorization service, or LoginActionsService, the workhorse behind every browser login step (form pages, action-token links, consent, broker callbacks). LoginActionsService funnels almost all of those endpoints through one processFlow() chokepoint that drives the AuthenticationProcessor engine, so a new engineer should read this module as 'how does an HTTP request become a call into the authentication engine'. It also shows where authorization (UMA/policy) and protocol-specific responses re-enter the same realm-scoped request pipeline.
getProtocol(): dispatching to a protocol's own endpoint
services/src/main/java/org/keycloak/services/resources/RealmsResource.java · lines 108–122No @GET/@POST here — this is a JAX-RS sub-resource locator, so it matches any HTTP method under {realm}/protocol/{protocol} and returns another object for JAX-RS to keep dispatching into.
First thing every locator does: resolve {realm} into a RealmModel and stash it on the session context so downstream code never has to look it up again.
Looks up the pluggable LoginProtocolFactory (oidc, saml) registered under the {protocol} segment; an unknown protocol name is a 404, not a 500.
Builds a per-request EventBuilder and hands off to the protocol factory to construct the actual endpoint object (e.g. OIDC's authorization/token endpoints), which JAX-RS then dispatches the real request into.
@Path("{realm}/protocol/{protocol}")public Object getProtocol(final @PathParam("realm") String name, final @PathParam("protocol") String protocol) { resolveRealmAndUpdateSession(name); LoginProtocolFactory factory = (LoginProtocolFactory)session.getKeycloakSessionFactory().getProviderFactory(LoginProtocol.class, protocol); if(factory == null){ logger.debugf("protocol %s not found", protocol); throw new NotFoundException("Protocol not found"); } EventBuilder event = new EventBuilder(session.getContext().getRealm(), session, session.getContext().getConnection()); return factory.createProtocolEndpoint(session, event);}
@Path("{realm}/login-actions")public LoginActionsService getLoginActionsService(final @PathParam("realm") String name) { resolveRealmAndUpdateSession(name); EventBuilder event = new EventBuilder(session.getContext().getRealm(), session, session.getContext().getConnection()); return new LoginActionsService(session, event);}
services/src/main/java/org/keycloak/services/resources/RealmsResource.java · lines 163–168authenticate(): the protocol-independent login-page endpoint
services/src/main/java/org/keycloak/services/resources/LoginActionsService.java · lines 324–354GET .../login-actions/authenticate is the endpoint the browser lands on after the OIDC/SAML authorization endpoint creates an authentication session and redirects into the login UI.
checksForCode() re-validates the session_code/tab_id/execution combination before anything else runs — the anti-CSRF and anti-replay guard shared by every login-actions endpoint.
Recovers the in-flight AuthenticationSessionModel plus whether the browser is submitting a form (an action) or just loading the page.
processAuthentication resolves the realm's configured browser flow and constructs a new AuthenticationProcessor to run it — the bridge from this HTTP layer into the authentication engine.
@Path(AUTHENTICATE_PATH)@GETpublic Response authenticate(@QueryParam(AUTH_SESSION_ID) String authSessionId, // optional, can get from cookie instead @QueryParam(SESSION_CODE) String code, @QueryParam(Constants.EXECUTION) String execution, @QueryParam(Constants.CLIENT_ID) String clientId, @QueryParam(Constants.TAB_ID) String tabId, @QueryParam(Constants.CLIENT_DATA) String clientData) { event.event(EventType.LOGIN); SessionCodeChecks checks = checksForCode(authSessionId, code, execution, clientId, tabId, clientData, AUTHENTICATE_PATH); if (!checks.verifyActiveAndValidAction(AuthenticationSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) { return checks.getResponse(); } AuthenticationSessionModel authSession = checks.getAuthenticationSession(); boolean actionRequest = checks.isActionRequest(); processLocaleParam(authSession); return processAuthentication(actionRequest, execution, authSession, null);}protected void processLocaleParam(AuthenticationSessionModel authSession) { LocaleUtil.processLocaleParam(session, realm, authSession);}protected Response processAuthentication(boolean action, String execution, AuthenticationSessionModel authSession, String errorMessage) { return processFlow(action, execution, authSession, AUTHENTICATE_PATH, AuthenticationFlowResolver.resolveBrowserFlow(authSession), errorMessage, new AuthenticationProcessor());}
protected Response processFlow(boolean action, String execution, AuthenticationSessionModel authSession, String flowPath, AuthenticationFlowModel flow, String errorMessage, AuthenticationProcessor processor) { processor.setAuthenticationSession(authSession) .setFlowPath(flowPath) .setBrowserFlow(true) .setFlowId(flow.getId()) .setConnection(clientConnection) .setEventBuilder(event) .setRealm(realm) .setSession(session) .setUriInfo(session.getContext().getUri()) .setRequest(request); if (errorMessage != null) { processor.setForwardedErrorMessage(new FormMessage(null, errorMessage)); } // Check the forwarded error message, which was set by previous HTTP request String forwardedErrorMessage = authSession.getAuthNote(FORWARDED_ERROR_MESSAGE_NOTE); if (forwardedErrorMessage != null) { authSession.removeAuthNote(FORWARDED_ERROR_MESSAGE_NOTE); processor.setForwardedErrorMessage(new FormMessage(null, forwardedErrorMessage)); } Response response; try { if (action) { response = processor.authenticationAction(execution); } else { response = processor.authenticate(); } } catch (WebApplicationException e) { response = e.getResponse(); authSession = processor.getAuthenticationSession(); } catch (Exception e) { response = processor.handleBrowserException(e); authSession = processor.getAuthenticationSession(); // Could be changed (eg. Forked flow) } return BrowserHistoryHelper.getInstance().saveResponseAndRedirect(session, authSession, response, action, request);}
services/src/main/java/org/keycloak/services/resources/LoginActionsService.java · lines 356–395action flag — set from whether the browser is submitting a form vs loading a page — decides whether the AuthenticationProcessor runs authenticationAction() (advance the flow with the submitted execution) or authenticate() (render the current step).Checkpoint: protocol & login endpoints
Answered in place — nothing is graded, everything is explained. 0 / 3 passed
What does every RealmsResource sub-resource locator (getProtocol, getLoginActionsService, getAuthorizationService, ...) do before anything else, and why?
In LoginActionsService.processFlow, what decides whether the AuthenticationProcessor runs authenticationAction(execution) versus authenticate()?
Why does LoginActionsService define a separate @HEAD handler (executeActionTokenHead) that just returns 200 with no body, alongside the @GET executeActionToken?