Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for WebAuthn Registration for anonymous user #16351

Open
justincranford opened this issue Dec 30, 2024 · 4 comments
Open

Support for WebAuthn Registration for anonymous user #16351

justincranford opened this issue Dec 30, 2024 · 4 comments
Assignees
Labels
in: web An issue in web modules (web, webmvc) status: feedback-provided Feedback has been provided type: enhancement A general enhancement

Comments

@justincranford
Copy link

I upgraded from Spring Security 6.3 to 6.4. I was using Yubico's WebAuthn, and I am in the process of trying to switch to Spring Security's WebAuthn. However, Spring Security WebAuthn is missing support for registration by an anonymous user. It is a blocker for switching.

Expected Behavior

WebAuthn L1 (2019) and L2 (2021) specifications support registration of a credential by an anonymous user. If the user doesn't exist, then registration is supposed to create the account before associating the credential to it.

Current Behavior

Visiting /webauthn/registration and /webauthn/registration/options fails due to the implementation looking at request.getRemoteUser(), and returning an error if found to be null.

Context

WebAuthn L2 Specification => https://www.w3.org/TR/2021/REC-webauthn-2-20210408

Subsection 1.3.1. Registration specifically says Or the user may be in the process of creating a new account. It is the last sentence from this excerpt.

The user visits example.com, which serves up a script.
At this point, the user may already be logged in using a legacy username and password, or additional authenticator, or other means acceptable to the [Relying Party](https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#relying-party).
Or the user may be in the process of creating a new account.

Example 1

Yubico's demo website https://webauthn.io/ shows how registration by anonymous user is supposed to work. Note, as the user, can choose between two WebAuthn registration types (Non-Resident vs Resident) under Advanced Settings via this setting.

Discoverable Credential:

  1. Discouraged (Client wants Non-Resident/Non-Discoverable)
  2. Preferred (Client wants Resident/Discoverable, but fallback to Non-Resident/Non-Discoverable is OK)
  3. Required (Client wants Resident/Discoverable)
image

Passkeys is an alias for Resident/Discoverable added in the L2 spec, but the spec is backwards compatible with Non-Resident/Non-Discoverable.

Example 2

Yubico offers a Java WebAuthn Server. It comes with a demo you can run yourself and debug. It supports credential registration by an anonymous user too.

Example 3

I used Yubico's WebAuthn Server with Spring Security 6.3 in my own project.

It is a new project, only WebAuthn registration and authentication are supported, and there are no other "legacy" authentication methods. Anonymous registration works. In this screenshot, you can see I used Google Chrome. Chrome's Developer Tools supports WebAuthn virtual authenticators for testing, and you can see I registered multiple Non-Resident and Resident credentials.

image
@justincranford justincranford added status: waiting-for-triage An issue we've not yet triaged type: enhancement A general enhancement labels Dec 30, 2024
@rwinch
Copy link
Member

rwinch commented Jan 17, 2025

Response

You are correct that WebAuthnRegistrationFilter the user to be authenticated. As you are aware, this is critical to validate that the passkey is only associated to its owner.

When a new user is created we can validate that the passkey is only associated to its owner by ensuring that the user does not exist yet and creating the account at the same time the passkey is associated. The problem for Spring Security, is that it is unlikely to know how to perform the validation in this case. Most real applications customize how a user is created (e.g. where it is persisted) and the properties on the account. For this reason, WebAuthnRegistrationFilter does not (and I expect it will never) support registering a passkey for an unauthenticated user.

If you have ideas on how to make this possible, I'm open to hearing it.

How to Register passkeys for an unauthenticated user

The good news is that there are at least two ways you can register a passkey for an unauthenticated user. The first way is probably the easiest, but the second one is preferred due to some downsides to the first option.

Split Registration into Multiple Steps

You can split registration into multiple steps. This method is probably the easiest, but it has some drawbacks that I'll mention at the end.

First you create the user and then mark the user as authenticated. You can mark the user as authenticated using something like this:

// You could also create a custom Authentication like NewAccountAuthentication
Authentication authentication = new TestingAuthenticationToken(newUsernameFromRegistration, null, "ROLE_USER");
SecurityContextHolder.setContext(new SecurityContextImpl(authentication));

On the next page you would register as you normally would with the build in WebAuthnRegistrationFilter.

The downside to this approach is that if a user performs the first step and then the HttpSession expires, then the user account is lost if you are only supporting passkeys for authentication.

Create Your Own Controller

The second option is a little more work, but it is more robust. In this option, you would create your own endpoints (controllers). This is similar to the Yubico demo which implements its own endpoints. As far as I know creating custom endpoints (and a CredentialRepository) is required in all cases when using the Yubico library.

The first endpoint will be an endpoint to create the options. For this, you can copy/modify the code in the PublicKeyCredentialCreationOptionsFilter. This will be simplified a bit if you implement it in a Controller since the conversions can be done automatically by Spring MVC.

The second endpoint will be your registration endpoint. It first performs the validation of the user and then creates the user and registers the passkey. You are able to reuse the logic in the WebAuthnRegistrationFilter. This is simplified a bit by the fact that Spring MVC can convert the request into your object types automatically for you.

@rwinch rwinch self-assigned this Jan 17, 2025
@rwinch rwinch added status: waiting-for-feedback We need additional information before we can continue in: web An issue in web modules (web, webmvc) and removed status: waiting-for-triage An issue we've not yet triaged labels Jan 17, 2025
@rwinch rwinch changed the title Spring Security WebAuthn is missing Registration by anonymous user, a use case specified in WebAuthn L2 Subsection 1.3.1 Support for WebAuthn Registration for anonymous user Jan 17, 2025
@spring-projects-issues
Copy link

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

@spring-projects-issues spring-projects-issues added the status: feedback-reminder We've sent a reminder that we need additional information before we can continue label Jan 24, 2025
@justincranford
Copy link
Author

If I understand correctly, the concern is about preventing hijacking the UserEntity.name?

  1. If UserEntity.name doesn't exist, anonymous user can register both UserEntity and Credential.
  2. If UserEntity.name exists, anonymous user can't register a Credential; that would hijack the username.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue status: feedback-reminder We've sent a reminder that we need additional information before we can continue labels Jan 24, 2025
@justincranford
Copy link
Author

When a registration request is submitted, I think of it as registering two things:

  1. Register UserEntity (i.e. id, name, displayName) - if UserEntity.name doesn't exist
  2. Register Credential - if Credential doesn't exist

I think WebAuthnRegistrationFilter only registers Credential. Potentially, it could be updated to register both? Or, add a new WebAuthnUserEntityRegistrationFilter that executes before WebAuthnRegistrationFilter?


You said:

Most real applications customize how a user is created

I think I understand. They can add custom filters, AuthenticationProviders, etc. There is no way for Spring Security to know where to look.

To fix that, give Developers a hook in the UserEntity registration flow.

  1. UserEntity check:
    a) If request is authenticated, and authentication principal == UserEntity.name, then proceed to Credential.
    b) If request is unauthenticated, reject the request.

If Developers can override 1b:

  1. They can check if UserEntity.name is not claimed in all of their custom user repositories
  2. And, they can choose to insert UserEntity.name into their custom user repositories, with defaults they choose.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web An issue in web modules (web, webmvc) status: feedback-provided Feedback has been provided type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

3 participants