WebAuthN as second factor logic? #38395
Replies: 5 comments 18 replies
-
We don't have any examples of that, no. Just to be clear, you want to use WebAuthn as a second required authentication method, on top on a primary one? Like, both must match? Or as an alternate login method, where you could login with either password or OIDC or WebAuhtn? This last case should be easy, it's mostly just having the UI to allow both types of logins, and the UI required to add password or WebAuthn auth when you're already logged in, to configure the alternate authentication options. |
Beta Was this translation helpful? Give feedback.
-
@FroMage for some more use case context: I am looking at scenarios where specific endpoints require the additional webauthn validation. So normal logic may require typical oidc. But specific endpoints would require the webauthn / security key in addition to the jwt. |
Beta Was this translation helpful? Give feedback.
-
WebAuthn as a second factor logic is definitely where OIDC is good. For example, Keycloak docs tals about it. Using WebAuthn at the OIDC level will also give the regular OIDC tokens. |
Beta Was this translation helpful? Give feedback.
-
@FroMage @sberyozkin I have reviewed the code being implemented in: and compared against: https://quarkus.io/guides/security-webauthn#handling-login-and-registration-endpoints-yourself and the guide seems to be wrong. The guide provides a /login endpoint example for handling logic yourself, but when comparing the code against the WebAuthnController.java file, it looks like the guide example ~recreated a version of callback instead of login? I re-create some quick impl of the 3 endpoints provided by default but re-created so users can modify inputs, etc. @Inject
WebAuthnSecurity webAuthnSecurity;
@Path("/webauthn/login")
@POST
@Transactional
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Uni<RestResponse<Map<String, Object>>> login(JsonObject body,
RoutingContext ctx,
@Context WebAuthnAuthenticationMechanism authMech) {
var username = body.getString("name");
return Uni.createFrom().completionStage(webAuthnSecurity.getWebAuthn().getCredentialsOptions(username).toCompletionStage())
.onFailure().transform(t -> new AuthenticationFailedException(t.getMessage(), t))
.map(getAssertion -> {
authMech.getLoginManager().save(getAssertion.getString("challenge"), ctx, CHALLENGE_COOKIE, null, ctx.request().isSSL());
authMech.getLoginManager().save(username, ctx, USERNAME_COOKIE, null, ctx.request().isSSL());
// Need to encode and reparse to get rid of the nested JsonObject classes causing unexpected serialization behaviour
// based on https://github.com/eclipse-vertx/vert.x/issues/1637#issuecomment-248998151
return new JsonObject(getAssertion.encode());
}).map(json -> RestResponse.ok(json.getMap()))
.onFailure(AuthenticationFailedException.class)
.recoverWithItem(t -> RestResponse.status(RestResponse.Status.BAD_REQUEST, Map.of("error", t.getMessage())));
}
@Path("/webauthn/register")
@POST
@Transactional
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Uni<RestResponse<Map<String, Object>>> register(JsonObject body,
RoutingContext ctx,
@Context WebAuthnAuthenticationMechanism authMech) {
return Uni.createFrom().completionStage(webAuthnSecurity.getWebAuthn().createCredentialsOptions(body).toCompletionStage()).map(credentialsOptions -> {
// save challenge to the session
authMech.getLoginManager().save(credentialsOptions.getString("challenge"), ctx, CHALLENGE_COOKIE, null,
ctx.request().isSSL());
authMech.getLoginManager().save(body.getString("name"), ctx, USERNAME_COOKIE, null,
ctx.request().isSSL());
return new JsonObject(credentialsOptions.encode());
}).map(json -> RestResponse.ok(json.getMap()));
}
@Path("/webauthn/callback")
@POST
@Transactional
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Uni<RestResponse<Void>> register(JsonObject webauthnResp,
RoutingContext ctx,
@Context WebAuthnAuthenticationMechanism authMech,
@Context WebAuthnRunTimeConfig config,
@Context IdentityProviderManager identityProviderManager) {
PersistentLoginManager.RestoreResult challenge = authMech.getLoginManager().restore(ctx, CHALLENGE_COOKIE);
PersistentLoginManager.RestoreResult username = authMech.getLoginManager().restore(ctx, USERNAME_COOKIE);
if (challenge == null || challenge.getPrincipal() == null || challenge.getPrincipal().isEmpty()
|| username == null || username.getPrincipal() == null || username.getPrincipal().isEmpty()) {
throw new IllegalArgumentException("Missing challenge or username");
}
var origin = config.origin.orElse(null);
String domain = null;
if (origin != null) {
Origin o = Origin.parse(origin);
domain = o.host();
}
WebAuthnCredentials credentials = new WebAuthnCredentials()
.setOrigin(origin)
.setDomain(domain)
.setChallenge(challenge.getPrincipal())
.setUsername(username.getPrincipal())
.setWebauthn(webauthnResp);
SecurityIdentity identity = identityProviderManager.authenticateBlocking(HttpSecurityUtils.setRoutingContextAttribute(new WebAuthnAuthenticationRequest(credentials), ctx));
// WebAuthnSecurity.removeCookie(ctx, WebAuthnController.CHALLENGE_COOKIE); // removeCookie Not a public method
Cookie challengeCookie = ctx.request().getCookie(WebAuthnController.CHALLENGE_COOKIE);
if (challengeCookie != null) {
challengeCookie.setPath("/");
}
ctx.response().removeCookie(WebAuthnController.CHALLENGE_COOKIE);
// WebAuthnSecurity.removeCookie(ctx, WebAuthnController.USERNAME_COOKIE); // removeCookie Not a public method
Cookie usernameCookie = ctx.request().getCookie(WebAuthnController.CHALLENGE_COOKIE);
if (usernameCookie != null) {
usernameCookie.setPath("/");
}
ctx.response().removeCookie(WebAuthnController.CHALLENGE_COOKIE);
authMech.getLoginManager().save(identity, ctx, null, ctx.request().isSSL());
return Uni.createFrom().item(RestResponse.ok());
} Can you clarify the usage from the guide, or is the guide old/out of date/incorrect? Thanks! |
Beta Was this translation helpful? Give feedback.
-
I'm more and more convinced we should have exposed the following endpoints instead:
Because there's a lot of confusion as to which endpoint does what, |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
I am looking through https://quarkus.io/guides/security-webauthn, and it uses webauthn as the primary login.
Is there any examples of config needed to make it a form of second factor auth?
Beta Was this translation helpful? Give feedback.
All reactions