mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-10 15:32:05 -03:30
Skip AIA for webauthn register if a crendential of teh correct type already exists
Closes #39191 Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
parent
72b0df7bee
commit
3c511635ba
@ -29,3 +29,7 @@ details, see link:{upgradingguide_link}[{upgradingguide_name}].
|
||||
= Recovery Codes supported
|
||||
|
||||
In this release, the *Recovery Codes* two-factor authentication is promoted from preview to supported feature. For more information about the 2FA method, see the link:{adminguide_link}#_recovery-codes[Recovery Codes] chapter in the {adminguide_name}.
|
||||
|
||||
= New AIA action parameter `skip_if_exists` for WebAuthn register
|
||||
|
||||
Both WebAuthn Register actions (`webauthn-register` and `webauthn-register-passwordless`) now support a parameter `skip_if_exists` when initiated by the application (AIA). The parameter allows to skip the action if the user already has a credential of that type. For more information, see the link:{adminguide_link}#_webauthn_aia[Registering WebAuthn credentials using AIA] chapter in the {adminguide_name}.
|
||||
|
||||
@ -191,6 +191,13 @@ If `WebAuthn Authenticator` is set up as required as shown in the first example,
|
||||
|
||||
After successful registration, the user's browser asks the user to enter the text of their WebAuthn authenticator's label.
|
||||
|
||||
[[_webauthn_aia]]
|
||||
==== Registering WebAuthn credentials using AIA
|
||||
|
||||
WebAuthn credentials can also be registered for a user using <<con-aia_{context},Application Initiated Actions (AIA)>>. The actions *Webauthn Register* (`kc_action=webauthn-register`) and *Webauthn Register Passwordless* (`kc_action=webauthn-register-passwordless`) are available for the applications if enabled in the <<proc-setting-default-required-actions_{context}, Required actions tab>>.
|
||||
|
||||
Both required actions allow a parameter *skip_if_exists* that allows to skip the AIA execution if the user already has a credential of that type. The `kc_action_status` will be *success* if skipped. For example, adding the option to the common WebAuthn register action is just using the following query parameter `kc_action=webauthn-register:skip_if_exists`.
|
||||
|
||||
[[_webauthn_passwordless]]
|
||||
==== Passwordless WebAuthn together with Two-Factor
|
||||
|
||||
|
||||
@ -87,6 +87,8 @@ public final class Constants {
|
||||
public static final String KC_ACTION = "kc_action";
|
||||
|
||||
public static final String KC_ACTION_PARAMETER = "kc_action_parameter";
|
||||
// parameter used by some actions to skip executing it if a credential for that type already exists for the user
|
||||
public static final String KC_ACTION_PARAMETER_SKIP_IF_EXISTS = "skip_if_exists";
|
||||
public static final String KC_ACTION_STATUS = "kc_action_status";
|
||||
public static final String KC_ACTION_EXECUTING = "kc_action_executing";
|
||||
/**
|
||||
|
||||
@ -52,6 +52,7 @@ import org.keycloak.credential.WebAuthnCredentialProviderFactory;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.WebAuthnPolicy;
|
||||
@ -107,7 +108,14 @@ public class WebAuthnRegister implements RequiredActionProvider, CredentialRegis
|
||||
|
||||
@Override
|
||||
public void requiredActionChallenge(RequiredActionContext context) {
|
||||
String actionParameter = context.getAuthenticationSession().getClientNote(Constants.KC_ACTION_PARAMETER);
|
||||
UserModel userModel = context.getUser();
|
||||
if (Constants.KC_ACTION_PARAMETER_SKIP_IF_EXISTS.equals(actionParameter)
|
||||
&& userModel.credentialManager().getStoredCredentialsByTypeStream(getCredentialType()).findAny().isPresent()) {
|
||||
context.success();
|
||||
return;
|
||||
}
|
||||
|
||||
// Use standard UTF-8 charset to get bytes from string.
|
||||
// Otherwise the platform's default charset is used and it might cause problems later when
|
||||
// decoded on different system.
|
||||
|
||||
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2025 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.testsuite.webauthn;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.credential.WebAuthnCredentialModel;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.arquillian.annotation.IgnoreBrowserDriver;
|
||||
import org.openqa.selenium.firefox.FirefoxDriver;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author rmartinc
|
||||
*/
|
||||
public class AppInitiatedActionWebAuthnSkipIfExistsTest extends AppInitiatedActionWebAuthnTest {
|
||||
|
||||
@Override
|
||||
public String getAiaAction() {
|
||||
return WEB_AUTHN_REGISTER_PROVIDER + ":" + Constants.KC_ACTION_PARAMETER_SKIP_IF_EXISTS;
|
||||
}
|
||||
|
||||
public String getCredentialType() {
|
||||
return isPasswordless() ? WebAuthnCredentialModel.TYPE_PASSWORDLESS : WebAuthnCredentialModel.TYPE_TWOFACTOR;
|
||||
}
|
||||
|
||||
@Test
|
||||
@IgnoreBrowserDriver(FirefoxDriver.class) // See https://github.com/keycloak/keycloak/issues/10368
|
||||
public void processSetupTwice() throws IOException {
|
||||
testWebAuthnLogoutOtherSessions(false);
|
||||
final long credentialsCount = ApiUtil.findUserByUsernameId(testRealm(), DEFAULT_USERNAME)
|
||||
.credentials()
|
||||
.stream()
|
||||
.filter(c -> c.getType().equals(getCredentialType()))
|
||||
.count();
|
||||
assertThat(credentialsCount, greaterThan(0L));
|
||||
|
||||
// do a second AIA that should be skiped
|
||||
doAIA();
|
||||
assertKcActionStatus(SUCCESS);
|
||||
|
||||
assertThat(ApiUtil.findUserByUsernameId(testRealm(), DEFAULT_USERNAME)
|
||||
.credentials()
|
||||
.stream()
|
||||
.filter(c -> c.getType().equals(getCredentialType()))
|
||||
.count(), is(credentialsCount));
|
||||
}
|
||||
}
|
||||
@ -180,7 +180,7 @@ public class AppInitiatedActionWebAuthnTest extends AbstractAppInitiatedActionTe
|
||||
testWebAuthnLogoutOtherSessions(false);
|
||||
}
|
||||
|
||||
private void testWebAuthnLogoutOtherSessions(boolean logoutOtherSessions) throws IOException {
|
||||
protected void testWebAuthnLogoutOtherSessions(boolean logoutOtherSessions) throws IOException {
|
||||
UserResource testUser = testRealm().users().get(findUser(DEFAULT_USERNAME).getId());
|
||||
|
||||
// perform a login using normal user/password form to have an old session
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2025 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.webauthn.passwordless;
|
||||
|
||||
import org.keycloak.testsuite.webauthn.AppInitiatedActionWebAuthnSkipIfExistsTest;
|
||||
|
||||
/**
|
||||
* @author rmartinc
|
||||
*/
|
||||
public class AppInitiatedActionPwdLessSkipIfExistsTest extends AppInitiatedActionWebAuthnSkipIfExistsTest {
|
||||
|
||||
@Override
|
||||
protected boolean isPasswordless() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user