Add support for Initiating User Registration via prompt=create (#10701) (#35903)

Fixes #10701

Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
This commit is contained in:
Thomas Darimont
2024-12-16 19:54:52 +01:00
committed by GitHub
parent cfdfd828ce
commit 3cdbbc5b15
6 changed files with 70 additions and 1 deletions

View File

@@ -73,6 +73,9 @@ public class OIDCConfigurationRepresentation {
@JsonProperty("subject_types_supported")
private List<String> subjectTypesSupported;
@JsonProperty("prompt_values_supported")
private List<String> promptValuesSupported;
@JsonProperty("id_token_signing_alg_values_supported")
private List<String> idTokenSigningAlgValuesSupported;
@@ -655,4 +658,11 @@ public class OIDCConfigurationRepresentation {
this.authorizationResponseIssParameterSupported = authorizationResponseIssParameterSupported;
}
public List<String> getPromptValuesSupported() {
return promptValuesSupported;
}
public void setPromptValuesSupported(List<String> promptValuesSupported) {
this.promptValuesSupported = promptValuesSupported;
}
}

View File

@@ -419,7 +419,8 @@ Sometimes it can be useful for the client application to directly redirect the u
user clicks *Register* or *Forget password* on the normal login screen. Automatic redirect to the registration or reset-credentials screen can be done as follows:
* When the client wants the user to be redirected directly to the registration, the OIDC client should replace the very last snippet from the OIDC login URL path (`/auth`) with `/registrations` . So the full URL
might be similar to the following: `https://keycloak.example.com/realms/your_realm/protocol/openid-connect/registrations`.
might be similar to the following: `https://keycloak.example.com/realms/your_realm/protocol/openid-connect/registrations`. As an alternative to using the `/registrations` endpoint, clients can use the OIDC
`prompt` parameter with `prompt=create` to redirect the user to the registration.
* When the client wants a user to be redirected directly to the `Reset credentials` flow, the OIDC client should replace the very last snippet from the OIDC login URL path (`/auth`) with `/forgot-credentials` .

View File

@@ -110,6 +110,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
public static final String PROMPT_VALUE_NONE = "none";
public static final String PROMPT_VALUE_LOGIN = "login";
public static final String PROMPT_VALUE_CONSENT = "consent";
public static final String PROMPT_VALUE_CREATE = "create";
public static final String PROMPT_VALUE_SELECT_ACCOUNT = "select_account";
// Client authentication methods

View File

@@ -91,6 +91,9 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
// KEYCLOAK-7451 OAuth Authorization Server Metadata for Proof Key for Code Exchange
public static final List<String> DEFAULT_CODE_CHALLENGE_METHODS_SUPPORTED = list(OAuth2Constants.PKCE_METHOD_PLAIN, OAuth2Constants.PKCE_METHOD_S256);
// See: GH-10701, note that the supported prompt value "create" is only added if the realm supports registrations.
public static final List<String> DEFAULT_PROMPT_VALUES_SUPPORTED = list(OIDCLoginProtocol.PROMPT_VALUE_NONE /*, OIDCLoginProtocol.PROMPT_VALUE_CREATE*/, OIDCLoginProtocol.PROMPT_VALUE_LOGIN, OIDCLoginProtocol.PROMPT_VALUE_CONSENT);
private final KeycloakSession session;
private final Map<String, Object> openidConfigOverride;
private final boolean includeClientScopes;
@@ -167,6 +170,8 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
config.setGrantTypesSupported(DEFAULT_GRANT_TYPES_SUPPORTED);
config.setAcrValuesSupported(getAcrValuesSupported(realm));
config.setPromptValuesSupported(getPromptValuesSupported(realm));
config.setTokenEndpointAuthMethodsSupported(getClientAuthMethodsSupported());
config.setTokenEndpointAuthSigningAlgValuesSupported(getSupportedClientSigningAlgorithms(false));
config.setIntrospectionEndpointAuthMethodsSupported(getClientAuthMethodsSupported());
@@ -235,6 +240,14 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
return config;
}
protected List<String> getPromptValuesSupported(RealmModel realm) {
List<String> prompts = new ArrayList<>(DEFAULT_PROMPT_VALUES_SUPPORTED);
if (realm.isRegistrationAllowed()) {
prompts.add(OIDCLoginProtocol.PROMPT_VALUE_CREATE);
}
return prompts;
}
@Override
public void close() {
}

View File

@@ -204,6 +204,16 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
// So back button doesn't work
CacheControlUtil.noBackButtonCacheControlHeader(session);
// Add support for Initiating User Registration via OpenID Connect 1.0 via prompt=create
// see: https://openid.net/specs/openid-connect-prompt-create-1_0.html#section-4.1
if (OIDCLoginProtocol.PROMPT_VALUE_CREATE.equals(params.getFirst(OAuth2Constants.PROMPT))) {
if (!Organizations.isRegistrationAllowed(session, realm)) {
throw new ErrorPageException(session, authenticationSession, Response.Status.BAD_REQUEST, Messages.REGISTRATION_NOT_ALLOWED);
}
return buildRegister();
}
switch (action) {
case REGISTER:
return buildRegister();

View File

@@ -80,7 +80,9 @@ import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.ErrorPage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.OAuthGrantPage;
import org.keycloak.testsuite.pages.RegisterPage;
import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource;
import org.keycloak.testsuite.util.RealmManager;
import org.keycloak.util.JWKSUtils;
import org.keycloak.util.JsonSerialization;
import org.keycloak.testsuite.util.OAuthClient;
@@ -124,6 +126,9 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
@Page
protected AppPage appPage;
@Page
protected RegisterPage registerPage;
@Page
protected LoginPage loginPage;
@@ -406,6 +411,35 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
Assert.assertEquals(oldIdToken.getSessionState(), newIdToken.getSessionState());
}
// prompt=create
@Test
public void promptCreate() {
// Assert registration page with prompt=login
driver.navigate().to(oauth.getLoginFormUrl() + "&prompt=create");
registerPage.assertCurrent();
}
// prompt=create
@Test
public void promptCreateShouldFailWhenRegistrationsAreDisabled() {
RealmRepresentation realmRep = adminClient.realm("test").toRepresentation();
Boolean registrationAllowed = realmRep.isRegistrationAllowed();
realmRep.setRegistrationAllowed(false);
adminClient.realm("test").update(realmRep);
// Assert registration page with prompt=login
try {
driver.navigate().to(oauth.getLoginFormUrl() + "&prompt=create");
errorPage.assertCurrent();
assertTrue(errorPage.getError().contains("Registration not allowed"));
} finally {
realmRep.setRegistrationAllowed(registrationAllowed);
adminClient.realm("test").update(realmRep);
}
}
// prompt=consent
@Test
public void promptConsent() {