I'm writing a passport strategy for W3C WebAuthn, which does away with passwords in favor of using a cryptographic challenge / response.
The basic algorithm is that the server is expected to send a random string to the client / browser and the browser will use a user's previously registered private key sign the random string and send the signature back to the server -- thus authenticating the user by being in possession of the user's private key.
The problem is that passport assumes a single message from the client to the server for authentication (containing a password), not a two-step challenge / response where a client has to request information from a server before being able to authenticate. There needs to be a separate API call for getting a challenge (most likely in a JSON format) without effecting the state of passport.
Options for fixing this seem to be:
passport.authenticate() for the response. Downside is that it requires every user to re-write the same code for managing challenges and breaks the beautiful simplicity of passport.passport.authorize() method for getting challenges. Probably confusing to passport users because it is semantically incorrect, but maybe it would work?passport.getChallenge() method for getting challenges. Assumes that there are going to be other frameworks that require challenge / response (which doesn't seem unlikely) and adds a new method the core framework, which I assume is something that shouldn't be taken lightly.Any thoughts / preferences of which way to go?
Thinking about this a bit more, there seems to be a 4th option:
.success(), .fail(), .redirect(), etc., maybe called .info(), that returns the arguments to the client without modification and doesn't indicate either success or failure. This might actually be the most reasonable approach.I've read over the W3C spec, and from my understanding the authentication is by virtue of verifying a public/private key pair. The examples the W3C gives are fairly unclear as to what the challenge is used for. They go so far as to just embed a challenge into the JavaScript executing on the client. Is there any other guidance as to how the challenge is used?
A couple questions:
Require a user manually create a new route for getting challenges, write all the code for managing challenges, and then just use passport.authenticate() for the response. Downside is that it requires every user to re-write the same code for managing challenges and breaks the beautiful simplicity of passport.
Why isn't this option valid? If the challenge is unrelated to actual authentication, it makes sense to scope that functionality out of this module. In my opinion, that preserves the simplicity of passport.
The W3C spec has the following text:
This attestation statement is delivered to the WebAuthn Relying Party by the WebAuthn Relying Party鈥檚 script running on the client, using methods outside the scope of this specification. It contains all the information that the WebAuthn Relying Party鈥檚 server requires to validate the statement, as well as to decode and validate the bindings of both the client and authenticator data.
The spec seems to focus only on the client-side platform, and leaves authentication by the server undefined. Such behavior is what Passport should be focusing on. Are there any recommendations as to how to deliver WebAuthn attestations to a server for authentication?
The examples the W3C gives are fairly unclear as to what the challenge is used for.
Getting into the weeds of the spec, makeCredential = registration; and getAssertion = authentication. Focusing on getAssertion, since that's what matters from a passport perspective, Section 4.1.2 says that the challenge gets passed down from the browser to an authenticator** through athenticatorGetAssertion and in Section 4.2.3 the authenticator signs a hash of clientData which includes the challenge. A signature of the hash proves that 1) an authenticator is in possession of a private key; and 2) that this is an approval of this specific instance (since the authenticator is signing a new random challenge each time).
Keep in mind that the first Review Draft of the W3C spec is just coming out on Tuesday, May 31st. I'm sure there is lots of clarification that will happen between now and then final version of the spec. The blog post from Microsoft might provide some clarity as well.
** authenticators are typically hardware like a YubiKey or an Android phone. For a first version of a software-only authenticator, check out the webauthn-soft-authn that I've been working on (although it's not nearly complete).
Why isn't this option valid? If the challenge is unrelated to actual authentication [...]
Well, the challenge is related to authentication. In theory, you could have different routes for getting a challenge and returning the results of getAttestation and then the strategy could install different middleware for each. In practice it would be good to have a single route for WebAuthn authentication, where the first call to that route would return a challenge and the second call to that route would return a signed challenge.
That could be managed through something like a custom callback, but that would require the user to write code like this every time:
app.get('/auth/webauthn', function(req, res, next) {
passport.authenticate('webauthn', function(err, user, info) {
// check whether this is a request for a challenge or a signed challenge
// if it's a request for a challenge, generate the challenge and return res.json()
// if it's a signed challenge lookup the public key for the user and verify the signature
})(req, res, next);
});
True, every user could cut and paste that same route code in there every time... but that seems to defeat the purpose of using passport. Personally, I would prefer something that looks like:
app.get('/auth/webauthn', passport.authenticate('webauthn'))
The spec seems to focus only on the client-side platform [...]
Yes, and that's by design since W3C is only concerned with browsers. The original browser specs were drafted by FIDO and I suspect that FIDO will provide some specs / guidelines as to what servers are supposed to do. Eventually, a WebAuthn server might look something like this.
Btw, here is an example passport WebAuthn strategy and demo server using Jingo. The relevant parts of the Jingo demo are routes/auth.js and public/js/rp-app.js.
Apologies in advance for the rough state of the code.
Hi,
Is there an update on this?
I'm asking because:
I've seen the WebAuthn strategy plugin https://github.com/apowers313/passport-webauthn from @apowers313 but as it's 3 years old + this thread i'm a bit hesitant to try it out. Also, the strategy isn't in the list of passportjs packages http://www.passportjs.org/packages/ which makes me even more hesitant.
I'm very eager to start using this on some pet project sites, but currently using WebAuthn as a method of login/registration is quite a lot of code. I can write it all, but i definitely prefer to use a library that takes that away for me.
And here i seem to have both relevant parties involved, hence the question :)
@markg85 I never published it because it requires PR #489 to be merged. I agree that it seems like the time is right to come back to this.
Most helpful comment
@markg85 I never published it because it requires PR #489 to be merged. I agree that it seems like the time is right to come back to this.