Closed mark-ellul closed 2 years ago
I've been thinking about this too. It's great we can do logins entirely client-side using Credential Management APIs, but how can we prevent abuse of that? Anything on the client can be faked, after all.
One thing that helps is, when you create a key, you specify callback URL(s) where the provider will POST to your backend with details.
That said, Credential Management APIs let us skip the OAuth flow entirely, so you wouldn't get POSTs if credential management is used on subsequent login.
Let me experiment with this today and get back to you shortly.
I agree the best solution is probably to send tokens on each signin-completed
event. Those tokens might be stale (e.g. subsequent sign-in using stored credential); your backend would be responsible for validating or refreshing them as needed.
I'll see if I can whip up some changes today to send tokens on each sign-in. I'll update here with the results.
@JudahGabriel thanks for the confirmation of the issue, I thought I was missing something obvious when I started to try and use pwa-auth in a pet project. Let me know when there is something to test.
OK, so here's what I'm thinking. pwa-auth will be updated so that you always get an authToken
. The token will be generated by the provider. It might be stale (e.g. if the user signs-in with previously-saved credentials).
The app flow would look something like this:
authToken
from the sign-in provider inside the signin-completed
eventauthToken
with Facebook. If the token is valid, create the user, and add the authToken to a list of validated tokens for this user.authToken
is already in the list of validated tokens. If not, validate the token with Facebook. If it's a valid token, add it to the list of valid tokens for this user, and allow the sign-in.What do you think?
Thinking about this further, I have a better idea: pwa-auth will always return an authToken
and refreshToken
. Those can always be validated on the server. 👍
Flow would look like this:
authToken
and refreshToken
inside the signin-completed
event.authToken
. If it's not valid (or expired), try to get a new token using refreshToken
.I think this is the simplest plan and works well with existing backend sign-in flows.
@JudahGabriel that sounds great.
The way @bevry is thinking of doing this validation for adding discourse sso to this library is:
I've updated pwa-auth to 0.9.7 so that sign-in always returns an accessToken and provider data.
const pwaAuth = document.querySelector("pwa-auth");
pwaAuth.addEventListener("signin-completed", ev => {
const signIn = ev.detail;
if (signIn.error) {
console.error("Sign in failed", signIn.error);
} else {
console.log("Email", signIn.email);
console.log("Access token", signIn.accessToken);
console.log("Access token expiration date", signIn.accessTokenExpiration);
console.log("Provider-specific data", signIn.providerData); // will contain access token, idToken, nonce, etc.
}
});
If the user went through OAuth popup, you'll get a new access token. If the user signed-in with a previously saved credential, you'll get the previous access token, which might be expired.
Your next question might be, "How do I verify an access token on my backend?" How you do that is provider-specific. I'm writing up a doc on how to do that for each provider. I'll update here when the doc is available.
Discourse, and apparently wordpress, don't provide an access token upon sso login, only user details.
https://meta.discourse.org/t/using-discourse-as-a-sso-provider/32974/101?u=balupton
I guess in this case, the access token would be one generated by my own app.
Interesting, @balupton.
The good news is, as of 0.9.7, pwa-auth will return the raw providerData
to handle scenarios where you need something besides an access token. Then you can use that on your backend to validate the sign-in: if the sign-in is valid, proceed with creating a user in the database, setting an auth cookie, etc. Or if the sign-in is invalid, boot the user back to sign-in.
As I'm building this "here's how to validate a sign-in on your backend" document, I'm seeing wildly different ways of doing this. For example, Google is super flexible: just do an HTTP GET and pass the id_token (not the access token!). But Apple? They require you to do a POST with your secret key (which can be downloaded only once!), send them signed bits that involve the nonce. It's really complicated. One little wrinkle in all this is, all the providers support POSTing back to your backend when a sign-in happens. That may factor into our final design for 1.0.
Once I finish this document, I'll have a clear view of how to verify a sign-in on your backend, and that will likely influence the API of pwa-auth.
@JudahGabriel Any update on this?User authentication is actually unusable without this feature
We're now sending the auth token back to you at login, as described here. You can validate that with Google, Microsoft, Apple, or Facebook.
I will write up some documentation on how to do that, but in the meantime, feel free to look at Google / Apple / MS / FB docs for validating tokens.
We're now sending the auth token back to you at login, as described here. You can validate that with Google, Microsoft, Apple, or Facebook.
I will write up some documentation on how to do that, but in the meantime, feel free to look at Google / Apple / MS / FB docs for validating tokens.
Could you please provide the URL of the docs for the providers here so that I can look at the docs myself while you prepare the documentation to be added to this repo.
Docs:
I'll update these soon to explain how to verify a key server-side.
@JudahGabriel you're probably already on the ball on this:
A few updates from Apple:
https://developer.apple.com/news/?id=e4u1mtfu
Users can now migrate existing accounts to Signin with Apple. Not sure if that is something that can be leveraged here.
I'm also think it would be cool to use a magic email link service, then use webauthn (which now supports faceid and touchid) to bypass the email logins.
Yeah, saw those announcements. I'd like to investigate the webauthn support for fingerprint and face recognition. That'd be super cool to enable sign-in using that. I'll see if there's anything that needs to be done on our end.
I'm not sure about FB and MS, but Google and Apple both provide id_tokens which would be the ideal value to use IMO when using something like pwa-auth for pure auth (eg when you don't need data from their services).
id_tokens
will be returned to you in providerData
during the signin-completed
event.
I'm currently using https://github.com/jmreyes/passport-google-id-token on the back-end - the process should be very similar for FB, not sure about others
After reading all above, for now I validate the ID token instead of the access token (for Microsoft / AAD this is described in detail here: https://tsmatz.wordpress.com/2016/03/08/azure-ad-msa-v2-endpoint-validate-id_token/). As soon as I'm sure that the ID token is valid and not tampered I furthermore check the token is not expired (claims field exp) or has been issued in the last few minutes (claims field iat). It furthermore should have been issued for your application, so the client ID of your app should match ID token claims field aud - in access token this is appid. When the provider data contains an access token, this can be used (after validating the same way). Otherwise my app creates an own access token / refresh token (libraries to the rescue).
While the above method works with Microsoft tokens, it possibly fails for other providers. I will investigate if this way works for other providers by adjusting the parameters mentioned above.
Hello mark-ellul, thank you for opening an issue with us!
I have automatically added a "needs triage" label to help get things started. Our team will investigate the issue and help solve it ASAP. Other community members may also look into the issue and provide feedback 🙌
Closing issue since it's been a while since there was a comment. For questions or discussion, please use the discord server. Any feature requests or bugs should open a new issue.
Hi,
First of all I really love this library. One thing that is stopping me from using it in a production system is the fact that on a backend there is no tokens passed on EACH login, I know on the first login there was raw content passed back.
So if I create a backend that would receive the user's details as a post request, how could I on a backend confirm that they user is who they say they are?
Is it possible to get access tokens returned on the signin-completed EACH time it is triggered? Then in my backend knowing the provider and the access token I can make a request to that provider to confirm that the user is who they say the are?
Thanks and Regards
Mark