-
Notifications
You must be signed in to change notification settings - Fork 171
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
Add challengeUrl
#2152
Comments
And I assume the Given I have this boilerplate code in my repo I like this change. async function newChallenge() {
const challengeResponse = await fetch("/challenge", {
method: "POST",
headers: { "Accept": "application/octet-stream" }
});
if (!challengeResponse.ok) {
throw new Error("Failed to fetch challenge");
}
const challenge = await challengeResponse.arrayBuffer();
return challenge;
}
async function getPasskey() {
return await navigator.credentials.get({
publicKey: {
challenge: (await newChallenge()),
allowCredentials: [],
userVerification: "required",
}
});
} |
Another option could be that we change the type of The promise then gets executed asynchronously with the UI showing. This solves some issues with having to pull in HTTP semantics into the API. |
The issue has all but been closed since then. In the WAWG discussion at TPAC yesterday the challenge URL was understood to be a better pattern for mobile use cases as well, so that whether you're in a browser or a native app an RP could allow for parallelizable credential discovery and challenge requesting to significantly improve some at-scale passkey auth scenarios (the idea is |
Why do challenges need to be fetched from the server? Couldn't the challenge also be generated client-side? This would reduce latency even further. Is the idea that mobile devices are not powerful enough to generate 16 bytes of entropy for the challenge? |
The server wouldn't be able to trust a challenge that was generated on the client. If the assertion was being replayed, the client would just replay the same challenge. |
You can get away with generating client-side challenges if they're based on a timestamp. Then, they could only be replayed within the period that they're valid. If you can accept that, then it's a great option for UX. |
Each step away from "randomly generated at the server" costs some bit of security:
|
In my implementation experience, challenges typically need to be associated with a particular session so that the server can verify that the assertion is signed over the expected challenge for that session. How would this association be expressed in a |
If the challenge is at least 16 bytes of random data as you recommend, then shouldn't that be enough to associate with a particular session since it's functionally globally unique? As long as the challenge is removed from memory of course. For example in my implementation, I have a hash table keyed by the 16-byte random challenges. This hash table contains the expiration of the challenge/ceremony. When a client sends a response, either the challenge is part of the hash table or not. If it is, it is removed from the hash table and the rest of the ceremony is completed. There is no "session id" involved. |
I have uploaded an explainer to the wiki with a set of proposed details on how this would work. |
I don't think a GET request is a good idea. As the request is not idempotent. It should be a POST. We might also want to think of adding |
Agreed @arianvp. HTTP semantics aside, there are countless situations where the proposal might not work for a given RP - including political/bureaucratic, non-technical reasons - and I'd be disappointed if only a subset could benefit from the improvements this change could yield. Any application today that a) DOES1 need any kind of authn to get their challenge and b) uses anything other than cookies to perform that authn (e.g. Authorization header) couldn't use this as proposed.
@MasterKale are you or others able to provide a bit more background here, especially for those of us that were not at TPAC? From the rest of your comment, I'm interpreting "better pattern" to mean "better pattern than I'd especially like help understanding how this impacts native apps at all, since they have their own platform-native APIs (e.g. the Implementation aside, I think it would also be worth adding another value into If possible, I think it's also worth adjusting the "fetch behavior" section to replace the create/get split with conditional/non-conditional mediation. This would allow conditional create to benefit in the same ways (reduced data loading when not needed, being able to have short challenge TTLs, etc) as conditional get. Footnotes
|
@arianvp's suggestion of using a POST makes sense, and I can update the explainer with that change.
That's true about Authorization headers but, generalizing a bit, I don't see a version of this where the request is as flexible as using the Fetch API directly, and sites still have the option of using that as they might be doing today. If there is a specific problem that RPs are going to often run into then we should probably try to accommodate that. Setting up an HTTP endpoint to serve random bytes and cache them in a session-keyed map doesn't seem like a terribly complicated thing to do, although perhaps there are constraints I'm not aware of. |
The challenge I see here is that any given backend stack tends to have its own unique requirements (this has certainly been the case at every company I've worked at), such that I expect relatively few would be able to benefit from this enhancement. Beyond what's already been discussed with sessions and authn, automatic CSRF mitigation rules applied by frameworks come to mind. As an alternative, I wonder if instead of accepting only a string, accepting any fetch resource parameter (i.e. If a string is passed, use that as you've already described with some default semantics; if you provide a Internally something like this: let request
if (typeof challengeUrl === "string") {
request = new Request(challengeUrl, {
headers: { Expect: 'application/octet-stream' },
method: 'POST',
// ... (others as specified)
})
} else {
request = challengeUrl
}
const response = await fetch(request)
if (!response.ok) {
// fail the webauthn process
}
const challenge = await response.arrayBuffer()
// continue as if a challenge had been provided directly Thoughts? |
That sounds like a reasonable thing that a browser could do, but someone in an offline discussion pointed out to me that this all has to be possible for the underlying platforms to implement as well. In cases where browsers pass requests through to platform WebAuthn APIs, it will be they who are fetching the challenge, not the browsers. This causes some problems, including for the explainer as currently written. For one, it means the request should not be credentialed. It is undesirable for browsers to be passing user session cookies, for example, to passkey providers. Also, while it might be possible to specify a set of arguments that RPs can add for certain special handling of the request (such as additional HTTP headers), passkey providers in general shouldn't be expected to have up-to-date implementations of the Fetch API, which would be implied if we allowed a resource |
I've somewhat expanded and modified the explainer, fleshing out some of the concerns I mentioned above. Unfortunately for this to be viable I think we have to make it considerably more restrictive for RPs, rather than less, for reasons that are now in the security section. I understand this might make it more difficult for RPs to deploy. The proposed constraints are:
RPs can use a query string in the URL to convey information to the challengeURL endpoint. |
WebAuthn challenges usually need to be fetched from the server. This introduces extra latency, especially in cases where the page is loaded from offline storage and apps. This extra latency delays when WebAuthn credentials can be shown to the user in an empty allow-list request.
Proposed Change
Add a
challengeUrl
parameter that lets authenticators (or user agents) asynchronously fetch the challenge. This would let browsers render the list of credentials before the challenge comes back, improving the user experience. Add feature detection for it.This obsoletes issue #1856.
The text was updated successfully, but these errors were encountered: