Git Product home page Git Product logo

webauthn4j-spring-security's Introduction

WebAuthn4J

WebAuthn4J

Actions Status Coverage Build Status license

A portable Java library for WebAuthn(Passkeys) server side verification

Conformance

All mandatory test cases and optional Android Key attestation test cases of FIDO2 Test Tools provided by FIDO Alliance are passed.

Supported Attestation statement format

All attestation statement formats are supported.

  • Packed attestation
  • FIDO U2F attestation
  • Android Key attestation
  • Android SafetyNet attestation
  • TPM attestation
  • Apple Anonymous attestation
  • None attestation
  • Apple App Attest attestation

Kotlin friendly

Although WebAuthn4J is written in Java, public members are marked by NotNull or Nullable annotation to declare nullability explicitly.

Projects using WebAuthn4J

Documentation

You can find out more details from the reference.

Getting from Maven Central

If you are using Maven, just add the webauthn4j as a dependency:

<properties>
  ...
  <!-- Use the latest version whenever possible. -->
  <webauthn4j.version>0.26.0.RELEASE</webauthn4j.version>
  ...
</properties>

<dependencies>
  ...
  <dependency>
    <groupId>com.webauthn4j</groupId>
    <artifactId>webauthn4j-core</artifactId>
    <version>${webauthn4j.version}</version>
  </dependency>
  ...
</dependencies>

Build from source

WebAuthn4J uses a Gradle based build system. In the instructions below, gradlew is invoked from the root of the source tree and serves as a cross-platform, self-contained bootstrap mechanism for the build.

Prerequisites

Java17 or later is required to build WebAuthn4J. To use WebAuthn4J library, JDK11 is OK if you don't need EdDSA support.

Checkout sources

git clone https://github.com/webauthn4j/webauthn4j

Build all jars

./gradlew build

How to use

Parse and Validation on WebAuthn registration

If your would like to verify Apple App Attest, please see the reference.

// Client properties
byte[] attestationObject = null /* set attestationObject */;
byte[] clientDataJSON = null /* set clientDataJSON */;
String clientExtensionJSON = null;  /* set clientExtensionJSON */
Set<String> transports = null /* set transports */;

// Server properties
Origin origin = null /* set origin */;
String rpId = null /* set rpId */;
Challenge challenge = null /* set challenge */;
byte[] tokenBindingId = null /* set tokenBindingId */;
ServerProperty serverProperty = new ServerProperty(origin, rpId, challenge, tokenBindingId);

// expectations
List<PublicKeyCredentialParameters> pubKeyCredParams = null;
boolean userVerificationRequired = false;
boolean userPresenceRequired = true;

RegistrationRequest registrationRequest = new RegistrationRequest(attestationObject, clientDataJSON, clientExtensionJSON, transports);
RegistrationParameters registrationParameters = new RegistrationParameters(serverProperty, pubKeyCredParams, userVerificationRequired, userPresenceRequired);
RegistrationData registrationData;
try {
    registrationData = webAuthnManager.parse(registrationRequest);
} catch (DataConversionException e) {
    // If you would like to handle WebAuthn data structure parse error, please catch DataConversionException
    throw e;
}
try {
    webAuthnManager.verify(registrationData, registrationParameters);
} catch (ValidationException e) {
    // If you would like to handle WebAuthn data validation error, please catch ValidationException
    throw e;
}

// please persist CredentialRecord object, which will be used in the authentication process.
CredentialRecord credentialRecord =
        new CredentialRecordImpl( // You may create your own CredentialRecord implementation to save friendly authenticator name
                registrationData.getAttestationObject(),
                registrationData.getCollectedClientData(),
                registrationData.getClientExtensions(),
                registrationData.getTransports()
        );
save(credentialRecord); // please persist credentialRecord in your manner

Parse and Validation on authentication

// Client properties
byte[] credentialId = null /* set credentialId */;
byte[] userHandle = null /* set userHandle */;
byte[] authenticatorData = null /* set authenticatorData */;
byte[] clientDataJSON = null /* set clientDataJSON */;
String clientExtensionJSON = null /* set clientExtensionJSON */;
byte[] signature = null /* set signature */;

// Server properties
Origin origin = null /* set origin */;
String rpId = null /* set rpId */;
Challenge challenge = null /* set challenge */;
byte[] tokenBindingId = null /* set tokenBindingId */;
ServerProperty serverProperty = new ServerProperty(origin, rpId, challenge, tokenBindingId);

// expectations
List<byte[]> allowCredentials = null;
boolean userVerificationRequired = true;
boolean userPresenceRequired = true;

CredentialRecord credentialRecord = load(credentialId); // please load authenticator object persisted in the registration process in your manner

AuthenticationRequest authenticationRequest =
        new AuthenticationRequest(
                credentialId,
                userHandle,
                authenticatorData,
                clientDataJSON,
                clientExtensionJSON,
                signature
        );
AuthenticationParameters authenticationParameters =
        new AuthenticationParameters(
                serverProperty,
                credentialRecord,
                allowCredentials,
                userVerificationRequired,
                userPresenceRequired
        );

AuthenticationData authenticationData;
try {
    authenticationData = webAuthnManager.parse(authenticationRequest);
} catch (DataConversionException e) {
    // If you would like to handle WebAuthn data structure parse error, please catch DataConversionException
    throw e;
}
try {
    webAuthnManager.verify(authenticationData, authenticationParameters);
} catch (ValidationException e) {
    // If you would like to handle WebAuthn data validation error, please catch ValidationException
    throw e;
}
// please update the counter of the authenticator record
updateCounter(
        authenticationData.getCredentialId(),
        authenticationData.getAuthenticatorData().getSignCount()
);

Sample application

WebAuthn4J Spring Security is built on the top of WebAuthn4J, and its sample application demonstrates WebAuthn4J feature well. Please see WebAuthn4J Spring Security sample application.

License

WebAuthn4J is Open Source software released under the Apache 2.0 license.

Contributing

Interested in helping out with WebAuthn4J? Great! Your participation in the community is much appreciated! Please feel free to open issues and send pull-requests.

webauthn4j-spring-security's People

Contributors

dependabot-preview[bot] avatar dependabot[bot] avatar github-actions[bot] avatar simon-ebner avatar tgits avatar trimoq avatar usr42 avatar xsg22 avatar ynojima avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

webauthn4j-spring-security's Issues

Should AttestationOptionsEndpointFilter be provided as a Controller?

based on webauthn4j/webauthn4j#700

Is your feature request related to a problem? Please describe.
I'm considering relying on this library to implement WebAuthn in our project. However, we encounter some difficulties in its integration. One of them is wiring WebAuthn4J inside an existing application with a complex security layer (e.g. multiple existing WebSecurityConfigurerAdapter).

While we initially added an additional WebSecurityConfigurerAdapter dedicated to WebAuthn4J purposes, it does not work as simply as that as AttestationOptionsEndpointFilter has to receive a proper Authentication (from a not-WebAuthn4J filter) to return a proper userId.

com.webauthn4j.springframework.security.options.AttestationOptionsProviderImpl.DefaultPublicKeyCredentialUserEntityProvider.provide(Authentication)

static class DefaultPublicKeyCredentialUserEntityProvider implements PublicKeyCredentialUserEntityProvider {

    @Override
    public PublicKeyCredentialUserEntity provide(Authentication authentication) {
        if(authentication == null){
            return null;
        }
        String username = authentication.getName();
        return new PublicKeyCredentialUserEntity(
                username.getBytes(StandardCharsets.UTF_8),
                username,
                username
        );
    }
}

Instead of making more complex our existing WebSecurityConfigurerAdapter, I would rather adding AttestationOptionsEndpointFilter logic as a Controller, through the existing securityFilterChains.

Describe the solution you'd like
Be provided a Controller (or anything not requiring to modify existing Filters) to respond similarly to AttestationOptionsEndpointFilter.

Describe alternatives you've considered
Writing this, I feel like I can code given Controller by myself. But it is cumbersome to rely on the same configuration as the one in my WebAuthn4JWebSecurityConfigurerAdapter given it relies on a WebAuthnLoginConfigurer (which is not @bean friendly).

Additional context
(As a side-note, we encountered issues as given Filter respond a AttestationOptions even if the HttpMethod is not GET (which is unexpected especially if the method is OPTIONS)). Given a Controller, we sould pick esplicitely @GetMapping (and @PostMapping?)

samples/mpa does not work

  1. go to the registration screen
  2. click the Register authenticator button
  3. register biometrics with apple touch id (saved locally in chrome)
  4. passkey is saved in chrome browser but /signup form post on server is not called

No possibility to provide custom OriginValidator implementation

Since RegistrationDataValidator is created "manually" in the constructor of WebAuthnRegistrationManager class it is not possible to set a custom OriginValidator implementation in RegistrationDataValidator. I would need to provide my own that omits checks of port numbers when comparing origin values.

The same would apply to AuthenticationDataValidator which is created inside of WebAuthnAuthenticationManager class.

Firefox Support

Firefox does support WebAuthN login, and it has for quite some time now. There's no need to not allow the user to authenticate if they're using Firefox. Maybe just a version check instead.

### This repository is exploring how to accept your contribution ###

This repository is ultimately aiming to merge into Spring Security, but to send the code to Spring Security as a patch, all contributors need to sign the Pivotal CLA,
We are considering ways to ensure that everyone who contributed signed Pivotal's CLA.

We cannot accept your contributions for now, sorry.

"method did not exist" after update to Spring Security 6

After updating to Spring Boot 3 with Spring Security 6.0.1, I'm getting the following error in WebAuthnLoginConfigurer at application start:

***************************
APPLICATION FAILED TO START
***************************

Description:

An attempt was made to call a method that does not exist. The attempt was made from the following location:

    com.webauthn4j.springframework.security.config.configurers.WebAuthnLoginConfigurer$AttestationOptionsEndpointConfig.configure(WebAuthnLoginConfigurer.java:489)

The following method did not exist:

    'org.springframework.security.config.annotation.web.HttpSecurityBuilder org.springframework.security.config.annotation.web.HttpSecurityBuilder.addFilterAfter(javax.servlet.Filter, java.lang.Class)'

The calling method's class, com.webauthn4j.springframework.security.config.configurers.WebAuthnLoginConfigurer$AttestationOptionsEndpointConfig, was loaded from the following location:

    jar:file:/D:/Users/user/.m2/repository/com/webauthn4j/webauthn4j-spring-security-core/0.7.6.RELEASE/webauthn4j-spring-security-core-0.7.6.RELEASE.jar!/com/webauthn4j/springframework/security/config/configurers/WebAuthnLoginConfigurer$AttestationOptionsEndpointConfig.class

The called method's class, org.springframework.security.config.annotation.web.HttpSecurityBuilder, is available from the following locations:

    jar:file:/D:/Users/user/.m2/repository/org/springframework/security/spring-security-config/6.0.1/spring-security-config-6.0.1.jar!/org/springframework/security/config/annotation/web/HttpSecurityBuilder.class

The called method's class hierarchy was loaded from the following locations:

    org.springframework.security.config.annotation.web.HttpSecurityBuilder: file:/D:/Users/user/.m2/repository/org/springframework/security/spring-security-config/6.0.1/spring-security-config-6.0.1.jar


Action:

Correct the classpath of your application so that it contains compatible versions of the classes com.webauthn4j.springframework.security.config.configurers.WebAuthnLoginConfigurer$AttestationOptionsEndpointConfig and org.springframework.security.config.annotation.web.HttpSecurityBuilder

Is webauthn4j-spring-security compatible with Spring Security 6? Any idea why this error occurs?

Gradle error > Task :samples:lib:spa-angular-client:npm_run_build FAILED

Hi when trying to build the project I get the above error. You can find the build scan here.

This is the command line output from ./gradlew samples:spa:bootRun:

> Task :samples:lib:spa-angular-client:npm_run_build FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':samples:lib:spa-angular-client:npm_run_build'.
> Process 'command '/home/chris/development/webauthn/webauthn4j-spring-security/samples/lib/spa-angular-client/.gradle/npm/npm-v8.1.4/bin/npm'' finished with non-zero exit value 127

Stacktrace:

* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':samples:lib:spa-angular-client:npm_run_build'.
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$executeIfValid$1(ExecuteActionsTaskExecuter.java:147)
        at org.gradle.internal.Try$Failure.ifSuccessfulOrElse(Try.java:282)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:145)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:133)
        at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:77)
        at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
        at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)
        at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
        at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)
        at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
        at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:74)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:333)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:320)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:313)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:299)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$run$0(DefaultPlanExecutor.java:143)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:227)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:218)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:140)
        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
        at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
Caused by: org.gradle.process.internal.ExecException: Process 'command '/home/chris/development/webauthn/webauthn4j-spring-security/samples/lib/spa-angular-client/.gradle/npm/npm-v8.1.4/bin/npm'' finished with non-zero exit value 127
        at org.gradle.process.internal.DefaultExecHandle$ExecResultImpl.assertNormalExitValue(DefaultExecHandle.java:414)
        at org.gradle.process.internal.DefaultExecAction.execute(DefaultExecAction.java:38)
        at org.gradle.process.internal.DefaultExecActionFactory.exec(DefaultExecActionFactory.java:205)
        at org.gradle.process.internal.DefaultExecOperations.exec(DefaultExecOperations.java:37)
        at com.github.gradle.node.util.DefaultProjectApiHelper.exec(ProjectApiHelper.kt:76)
        at com.github.gradle.node.exec.ExecRunner.execute(ExecRunner.kt:45)
        at com.github.gradle.node.npm.exec.NpmExecRunner.executeCommand(NpmExecRunner.kt:55)
        at com.github.gradle.node.npm.exec.NpmExecRunner.executeNpmCommand(NpmExecRunner.kt:24)
        at com.github.gradle.node.npm.task.NpmTask.exec(NpmTask.kt:73)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:104)
        at org.gradle.api.internal.project.taskfactory.StandardTaskAction.doExecute(StandardTaskAction.java:58)
        at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:51)
        at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:29)
        at org.gradle.api.internal.tasks.execution.TaskExecution$3.run(TaskExecution.java:242)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:68)
        at org.gradle.api.internal.tasks.execution.TaskExecution.executeAction(TaskExecution.java:227)
        at org.gradle.api.internal.tasks.execution.TaskExecution.executeActions(TaskExecution.java:210)
        at org.gradle.api.internal.tasks.execution.TaskExecution.executeWithPreviousOutputFiles(TaskExecution.java:193)
        at org.gradle.api.internal.tasks.execution.TaskExecution.execute(TaskExecution.java:171)
        at org.gradle.internal.execution.steps.ExecuteStep.executeInternal(ExecuteStep.java:89)
        at org.gradle.internal.execution.steps.ExecuteStep.access$000(ExecuteStep.java:40)
        at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:53)
        at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:50)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
        at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:50)
        at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:40)
        at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:68)
        at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:38)
        at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:48)
        at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:36)
        at org.gradle.internal.execution.steps.CancelExecutionStep.execute(CancelExecutionStep.java:41)
        at org.gradle.internal.execution.steps.TimeoutStep.executeWithoutTimeout(TimeoutStep.java:74)
        at org.gradle.internal.execution.steps.TimeoutStep.execute(TimeoutStep.java:55)
        at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:51)
        at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:29)
        at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.execute(CaptureStateAfterExecutionStep.java:61)
        at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.execute(CaptureStateAfterExecutionStep.java:42)
        at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:60)
        at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:27)
        at org.gradle.internal.execution.steps.BuildCacheStep.executeWithoutCache(BuildCacheStep.java:180)
        at org.gradle.internal.execution.steps.BuildCacheStep.lambda$execute$1(BuildCacheStep.java:75)
        at org.gradle.internal.Either$Right.fold(Either.java:175)
        at org.gradle.internal.execution.caching.CachingState.fold(CachingState.java:59)
        at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:73)
        at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:48)
        at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:36)
        at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:25)
        at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:36)
        at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:22)
        at org.gradle.internal.execution.steps.SkipUpToDateStep.executeBecause(SkipUpToDateStep.java:110)
        at org.gradle.internal.execution.steps.SkipUpToDateStep.lambda$execute$2(SkipUpToDateStep.java:56)
        at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:56)
        at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:38)
        at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:73)
        at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:44)
        at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:37)
        at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:27)
        at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:89)
        at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:50)
        at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:114)
        at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:57)
        at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:76)
        at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:50)
        at org.gradle.internal.execution.steps.SkipEmptyWorkStep.executeWithNoEmptySources(SkipEmptyWorkStep.java:249)
        at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:86)
        at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:54)
        at org.gradle.internal.execution.steps.RemoveUntrackedExecutionStateStep.execute(RemoveUntrackedExecutionStateStep.java:32)
        at org.gradle.internal.execution.steps.RemoveUntrackedExecutionStateStep.execute(RemoveUntrackedExecutionStateStep.java:21)
        at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep.execute(MarkSnapshottingInputsStartedStep.java:38)
        at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:43)
        at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:31)
        at org.gradle.internal.execution.steps.AssignWorkspaceStep.lambda$execute$0(AssignWorkspaceStep.java:40)
        at org.gradle.api.internal.tasks.execution.TaskExecution$4.withWorkspace(TaskExecution.java:287)
        at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:40)
        at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:30)
        at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:37)
        at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:27)
        at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:44)
        at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:33)
        at org.gradle.internal.execution.impl.DefaultExecutionEngine$1.execute(DefaultExecutionEngine.java:76)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:144)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:133)
        at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:77)
        at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
        at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)
        at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
        at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)
        at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
        at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:74)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:333)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:320)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:313)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:299)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$run$0(DefaultPlanExecutor.java:143)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:227)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:218)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:140)
        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
        at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)

My angular-error.log file is:

[error] Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/home/chris/development/webauthn/webauthn4j-spring-security/samples/lib/spa-angular-client/node_modules/@angular/compiler/fesm2015/compiler.mjs' imported from /home/chris/development/webauthn/webauthn4j-spring-security/samples/lib/spa-angular-client/node_modules/@angular/compiler-cli/bundles/index.js
    at new NodeError (node:internal/errors:371:5)
    at finalizeResolution (node:internal/modules/esm/resolve:416:11)
    at moduleResolve (node:internal/modules/esm/resolve:932:10)
    at defaultResolve (node:internal/modules/esm/resolve:1044:11)
    at ESMLoader.resolve (node:internal/modules/esm/loader:422:30)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:222:40)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:76:40)
    at link (node:internal/modules/esm/module_job:75:36)

Is origin validation spec conform in its current form?

Hi,

currently, the origin validation in OriginValidatorImpl uses the exact request URL (http(s)://server:port) for the validation of the origin.
I thought according to the specs (WebAuthn level 2), the validation of the origin should happen based on the RP ID, thus also allowing subdomains (e.g. for the rpId example.com, test.example.com would be allowed). Therefore, the protocol should not be matched, neither the port.
Furthermore, only https should be permissible.
How might one change this behavior without reimplementation in their code base? Currently i just provide another ServerPropertiesProvider with the "correct" URL

Is it possible to use reverse proxying with the current approach without having to reimplement the default behavior in my project?

Thanks

Fail to build

Hello everyone,

Congratulation, for this repository and your work on it.

I face a problem with the build
image

I face, also, another problem with the quick start command ./gradlew spring-security-webauthn-samples/javaconfig/webauthn/spa:bootRun
image

OriginValidator.validate failed when validate the request options from the android device by WebAuthnRegistrationRequestValidator

If the registration is sent from an Android device, it will have the format:

{
  "type": "webauthn.create",
  "challenge": "Vdpc4dM8SEKGCOv9gbBpTg",
  "origin": "android:apk-key-hash:-s dwJA3hvue3mKpYrOZ9zSPC7b4mbgzJmdZEDO5w",
  "androidPackageName": "com.foodeyfrontend"
}

The origin is no longer a domain address. Is there a way to change the implementation class of OriginValidator or to support additional origins for Android?

NoSuchMethodError with AuthenticatorDataConverter building and running on JDK 8

Hello,

I am getting the following error after running the samples:SPA (using gradlew samples:spa:bootRun ) application from the latest master repository cloned from https://github.com/webauthn4j/webauthn4j-spring-security.

JDK 8 version (1.8.0_322) was used to build and run the application.

Webauthn4j version used - 0.20.release and webauthn4j-spring-security was from the latest master.

During the signup process, following the creation of the authenticator data, when the 'register' button is clicked on the signup page, the following error occurs.

2022-06-28 14:05:37.720 INFO 21080 --- [ main] c.w.s.s.webauthn.sample.SampleSPA : Started SampleSPA in 7.892 seconds (JVM running for 8.586)
2022-06-28 14:05:37.720 INFO 21080 --- [ main] c.w.s.s.webauthn.sample.SampleSPA : Started SampleSPA in 7.892 seconds (JVM running for 8.586)
2022-06-28 14:05:45.840 INFO 21080 --- [nio-8080-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-06-28 14:05:45.841 INFO 21080 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2022-06-28 14:05:45.844 INFO 21080 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Completed initialization in 3 ms
2022-06-28 14:06:25.753 ERROR 21080 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.NoSuchMethodError: java.nio.ByteBuffer.position(I)Ljava/nio/ByteBuffer;] with root cause

java.lang.NoSuchMethodError: java.nio.ByteBuffer.position(I)Ljava/nio/ByteBuffer;
at com.webauthn4j.converter.AuthenticatorDataConverter.convertToExtensions(AuthenticatorDataConverter.java:205)
at com.webauthn4j.converter.AuthenticatorDataConverter.convert(AuthenticatorDataConverter.java:132)
at com.webauthn4j.converter.jackson.deserializer.cbor.AuthenticatorDataDeserializer.deserialize(AuthenticatorDataDeserializer.java:52)
at com.webauthn4j.converter.jackson.deserializer.cbor.AuthenticatorDataDeserializer.deserialize(AuthenticatorDataDeserializer.java:34)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:542)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:563)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeUsingPropertyBasedWithExternalTypeId(BeanDeserializer.java:1031)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeWithExternalTypeId(BeanDeserializer.java:929)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:349)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:184)
at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:323)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4674)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3690)
at com.webauthn4j.converter.util.CborConverter.readValue(CborConverter.java:55)
at com.webauthn4j.converter.AttestationObjectConverter.convert(AttestationObjectConverter.java:77)
at com.webauthn4j.converter.AttestationObjectConverter.convert(AttestationObjectConverter.java:62)
at com.webauthn4j.springframework.security.converter.Base64UrlStringToAttestationObjectConverter.convert(Base64UrlStringToAttestationObjectConverter.java:48)
at com.webauthn4j.springframework.security.webauthn.sample.app.util.jackson.deserializer.AuthenticatorObjectFormDeserializer.deserialize(AuthenticatorObjectFormDeserializer.java:48)
at com.webauthn4j.springframework.security.webauthn.sample.app.util.jackson.deserializer.AuthenticatorObjectFormDeserializer.deserialize(AuthenticatorObjectFormDeserializer.java:31)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:391)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:184)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer._deserializeFromArray(CollectionDeserializer.java:355)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:244)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:28)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:391)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:184)
at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:323)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4674)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3682)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:380)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:343)
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:185)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:160)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:133)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:179)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:146)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:665)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:750)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:327)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:115)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:81)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:122)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at com.webauthn4j.springframework.security.endpoint.AssertionOptionsEndpointFilter.doFilter(AssertionOptionsEndpointFilter.java:68)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at com.webauthn4j.springframework.security.endpoint.AttestationOptionsEndpointFilter.doFilter(AttestationOptionsEndpointFilter.java:64)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:126)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:81)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:109)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:149)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:223)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:217)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:132)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:112)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:82)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:211)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:183)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:890)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1743)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:750)
<============-> 94% EXECUTING [2m 15s]

The RpIdProvider interface dependency on HttpServletRequest may not be necessary

The RpIdProvider interface has a direct dependency on HttpServletRequest that may not be necessary.
As a matter of fact, with Spring we can get the curent request via utility classes relying on static methods.

The use of static methods is not ideal but it can allow heree to get read of a direct dependency on HttpServletRequest for the RpIdProvider interface.

It can open the possibility to alternative implementations of RpIdProvider that do not rely on the HttpServletRequest, without the need to have a useless parameter.

Such a refactoring may ease futur refactorings.

@angular/cdk version conflict

This error message appears when I run the "/usr/local/bin/npm install" command.

npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR! 
npm ERR! While resolving: [email protected]
npm ERR! Found: @angular/[email protected]
npm ERR! node_modules/@angular/common
npm ERR!   @angular/common@"^13.3.11" from the root project
npm ERR! 
npm ERR! Could not resolve dependency:
npm ERR! peer @angular/common@"^15.0.0 || ^16.0.0" from @angular/[email protected]
npm ERR! node_modules/@angular/cdk
npm ERR!   @angular/cdk@"^15.1.2" from the root project
npm ERR! 
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force, or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.

When I change the version of "@angular/cdk" from "15.1.2" to "13.3.9", the demo runs ok.

Mac OS support

I'm using macOS Catalina and https://github.com/github/SoftU2F as soft authenticator, yubico WebAuthn demo passed, both with latest stable chrome 78.0.3904.87 and firefox 70.0.1.
With spring-security-webauthn, both chrome and firefox works fine for registeration, but not working for login, chrome failed wake up SoftU2F, and firefox can wake up SoftU2F but failed with Authentication failed with InvalidStateError

The allowCredentials list is always empty with InMemoryWebAuthnAuthenticatorManager

Key type mismatch in AssertionOptionsProvider and InMemoryWebAuthnAuthenticatorManager

It looks like the AssertionOptionsProvider passes String as key for credentials lookup, when InMemoryWebAuthnAuthenticatorManager expect keys of type User. For this reason, the call

return getAuthenticatorService().loadAuthenticatorsByUserPrincipal(authentication.getName()).stream()

always returns the empty list.

Dependabot couldn't find a package.json for this project

Dependabot couldn't find a package.json for this project.

Dependabot requires a package.json to evaluate your project's current JavaScript dependencies. It had expected to find one at the path: /samples/javaconfig/webauthn/lib/spa-angular-client/package.json.

If this isn't a JavaScript project, or if it is a library, you may wish to disable updates for it from within Dependabot.

View the update logs.

CoreCredentialRecord incompatible with Lombok

All the methods in the interface CoreCredentialsRecord have different Parameters
e.g.

@Nullable Boolean isUvInitialized() ;
void setUvInitialized(boolean value);

This makes it incompatible with Lombok, because you cannot generate either Setters or Getters.
There is no reason to make the setters only accept primitive values since the getter is explicitely annotated to be Nullable.

also the "is" prefix should only be used for booleans, not for Boolean, see https://stackoverflow.com/a/69414543

Build fails.

At this commit, convertToString is renamed.
webauthn4j/webauthn4j@f93da0e#diff-177eb0f5da488a57a3608b0ac7e1e146

It causes the sample app build failure.

$ ./gradlew build
Starting a Gradle Daemon, 1 incompatible Daemon could not be reused, use --status for details

> Task :spring-security-webauthn-core:compileTestJava
/Users/uu091468/Desktop/spring-security-webauthn-master/core/src/test/java/net/sharplab/springframework/security/webauthn/converter/Base64UrlStringToAttestationObjectConverterTest.java:34: エラー: シンボルを見つけられません
        String source = new AttestationObjectConverter(cborConverter).convertToString(expected);

Broken API '/api/profile' after login with password

How to produce?

  • Checkout commit 2b063c1 and create new user account with password and then login via password authentication.
    The user profile API resource '/api/profile' fails with the following error.

`2023-10-16T19:27:14.681+02:00 ERROR 88233 --- [nio-8080-exec-9] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.orm.jpa.JpaSystemException: Error attempting to apply AttributeConverter] with root cause

java.lang.IllegalArgumentException: argument "src" is null
at com.fasterxml.jackson.databind.ObjectMapper._assertNotNull(ObjectMapper.java:4885)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3737)
at com.webauthn4j.converter.util.CborConverter.readValue(CborConverter.java:55)
at com.webauthn4j.springframework.security.webauthn.sample.infrastructure.util.jpa.converter.COSEKeyConverter.convertToEntityAttribute(COSEKeyConverter.java:43)
at com.webauthn4j.springframework.security.webauthn.sample.infrastructure.util.jpa.converter.COSEKeyConverter.convertToEntityAttribute(COSEKeyConverter.java:26)
at org.hibernate.metamodel.model.convert.internal.JpaAttributeConverterImpl.toDomainValue(JpaAttributeConverterImpl.java:86)
at org.hibernate.sql.results.graph.basic.BasicResultAssembler.assemble(BasicResultAssembler.java:80)
`

The Spring and Spring Security migration may be the issue here.
It could also relate to an issue with another library.

The commit f7680f8 from March this year works fine so far.

Implications

  • view profile information and updating it don't work

Introducing a ChallengeProvider

Introducing an interface ChallengeProvider to allow to refactor ChallengeRepository, in order to easily allow to have alternative Challenge implementations to DefaultChallenge.

MPA sample should use the username instead of user to create a WebAuthnAuthenticator

In the MPA sample, a WebAuthnAuthenticator gets created from the variable user.

WebAuthnAuthenticator authenticator = new WebAuthnAuthenticatorImpl(
"authenticator",
user,
registrationRequestValidationResponse.getAttestationObject().getAuthenticatorData().getAttestedCredentialData(),

But AssertionOptionsProviderImpl passes authentication.getName() to the WebAuthnAuthenticatorService when looking up credentials.

return getAuthenticatorService().loadAuthenticatorsByUserPrincipal(authentication.getName()).stream()
.map(authenticator -> new PublicKeyCredentialDescriptor(PublicKeyCredentialType.PUBLIC_KEY, authenticator.getAttestedCredentialData().getCredentialId(), authenticator.getTransports()))
.collect(Collectors.toList());

Since the in-memory implementation is used in the sample, we search for the name of the current Authentication in the map.

public List<WebAuthnAuthenticator> loadAuthenticatorsByUserPrincipal(Object userPrincipal) {
Map<String, WebAuthnAuthenticator> innerMap = map.get(userPrincipal);
if(innerMap == null || innerMap.isEmpty()){

Since the sample code stored the whole UsernamePasswordAuthenticationToken, this will never match and a user authenticated in a first step will not be presented with allowed credentials.

To allow matching to work, I would propose to change the sample code to use the users name instead of the whole user.
If you think this is the correct way, I am happy to create a PR for it.

Login JavaScript Error

Upon trying to login to the site with a registered token, I get the following JavaScript console error:

DOMException: "An attempt was made to use an object that is not, or is no longer, usable"

This is thrown from loginViewModel.js:48. This occurred when attempting to login with a registered token in Firefox. I spent some time trying to debug this to potentially find a solution, to no avail.

On top of that, when trying to login with Chrome, the token doesn't even blink.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.