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
Single page applications social logins e.g facebook, google etc #479
Comments
I solved this problem for my SPA, but I tend to believe I reinvented the wheel a bit as there is a bunch of code I had to write. In theory, the existing ExternalLogin stuff would just work but issue a token instead of a cookie. I couldn't figure out how to do that though. The gist for my solution is just to get an external token in the "normal" way on the client (ideally an id token, but in some cases an access token was all they offered) using the JS libs of that 3rd party. Then, I implemented the assertion grant flow on the server which took the assertion (id or access token), validated it using whatever mechanism the external provider provides (usually an API to validate the id token or for Facebook I had to just use the access token myself assuming that if it's usable it's valid). Once validated, I map the external identity to my site's identity and issue a token. One indirect benefit is that since the client gets the external provider's token, there aren't any secrets involved which simplifies deployment a bit. Here's my code: |
@PinpointTownes I can't find an easy way to generate authentication ticket. What I am trying to do is to authenticate user like we generate ticket during normal login , but after social login authentication callback. I want to generate similar ticket and send to client (e.g Angular in this case). In the login example we have passed in OpenIdConnectRequest which isn't available during social login redirect. I am wonding if there is a simple api available in openiddict-core to generate same authentication ticket by just passing in User Principal. Thanks |
Assuming external authentication is handled using the regular ASP.NET Core Facebook/Google/Twitter/whatver middleware, you need to implement an interactive flow like implicit: you won't be able to use the token endpoint - which is an API endpoint - will have to use the authorization endpoint: https://github.com/openiddict/openiddict-samples/blob/dev/samples/ImplicitFlow/AuthorizationServer/Controllers/AuthorizationController.cs |
@PinpointTownes Yes external authentication is handle by asp.net core. This is exact implementation that comes out of the box in asp.net core mvc templates and I am trying to use in my SPA template which is a modification of same template. As in this source:
But I thought instead of signing in here which does cookies authentication I will create authentication ticket and send back to client. So I have two questions:
Thanks for your help as always. |
No. OpenIddict won't allow you to return an OIDC response from an arbitrary endpoint - i.e your custom
|
@PinpointTownes following your answer on stackoverflow I have added Implicit flow in the application and corresponding authorisation endpoint. LocalRedirect is failing with following error:
Here is my code with left hand side account creation method which localredirect to connect/authorise endpoint and on the right hand side its that endpoing: Not sure if this is the right way. But I am passing in OIDC parameters in query string. Source code until this point is in this branch. Please note that this is a single project which self-hosts openidconnect server/aspnetcore api and angular application. |
I strongly encourage you to use the I think you're trying to do things in the wrong order. As explained in the SO post, the client application is supposed to redirect the user to the authorization endpoint, that will itself redirect the user to the external login page. Just call |
@PinpointTownes return url isn't the issue here. Since this is the Angular client application I have different client side routes after external login verification, therefore I don't populate returnUrl when external login even starts, therefore that is empty anyway. Challenge still exist in somehow making angular client to receive same ticket that we receive during password flow. I haven't seen any implementation of facebook/google authentication which uses purely token based authentication. Second challenge I am still trying to understand is that if an external user already has created previously how do we auto login them without using cookies? The only place this application is using cookies when we have to temporarily persist external login information until user creates a unique account with an email at that point VS templates sign in with cookies and this is exactly where I want to generate ticket instead, with async callbacks. This is slightly different way to authenticate comparing to actual VS templates. Apologies if I am unable to explain it well, but if you run the project itself, you'll perhaps understand what I am trying to do. |
I took a look at your branch, but without seeing the JS part, it's hard to see what you're trying to do 🤔 That said, the root cause of the error message you're seeing is simple: you've added simple quotes everywhere in the URL, which produces invalid parameters:
->
|
what is nonce parameter? error:invalid_request |
http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthRequest |
@PinpointTownes thanks. further progress :) I have managed to hit the connect/authorize endpoint, but it is getting IsAuthenticated as false. Even after external login success and the account already exist in database and ExternalLoginSignInAsync succeed and redirect to connect/authorize:
I suspect it isn't persisting the user information during local redirect. Any idea? |
You'll probably have to use |
Even I have only jwt as registered authentication schemes:
Not sure why this is null as well:
Confusing thing is that there is no cookies scheme registered throughout system, then how come _signInManager.GetExternalLoginInfoAsync() pulls the information of user without issue:
Any other way you get user info you recon during localredirect? Thanks |
@PinpointTownes with local redirect, Request Object has the External login cookie, therefore:
I am able to get info for external login in connect/authorize endpoint. But since I haven't added cookies as authentication scheme some of the user authentication apis e.g
don't persist User on local redirect. Perhaps I have to pull the user again using _signInManager.ExternalLoginSignInAsync in connect/authorize. |
@PinpointTownes I think what you said was absolutely right that I should just use return url to localredirect and let ImplicitFlow endpoint handle the signin logic. I will change that and show you once done. As a side note, do you know if it is possible to open external login page as a popup instead? Currently
this call just go to external login page within same window. |
@PinpointTownes sorry to bother you again. Finally I have got something working purely with JWT. Main files for these changes are AuthorizationController & AccountController. Would you mind reviewing these changes to point out if anything needs improving. Really appreciate your help in pointing out in the right direction. Thanks once again. |
@asadsahi I'll take a closer look today but it's still not clear for me why you're building the authorization request payload (the |
@PinpointTownes My application is token based. Therefore, mapping the same idea when user is redirected back to our site from social login page, there are two possibilities:
I might be doing something totally wrong, but angular spa and SignIn being the only way to generate ticket, I can't see any other alternative. A working demo is here. |
Here's the flow you're currently using: What I'm suggesting is to replace the first step by a redirection to the authorization endpoint directly made by the JS client (so that the /connect/authorize URL is generated by the JS app, not by your MVC controllers). If you want to redirect the user agent to the "selected external provider", you can easily tweak the authorization endpoint action to accept a custom "provider" parameter and redirect the user agent to the [HttpGet("~/connect/authorize")]
public async Task<IActionResult> Authorize(OpenIdConnectRequest request)
{
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
// If an identity provider was explicitly specified, redirect
// the user agent to the AccountController.ExternalLogin action.
var provider = (string) request["provider"];
if (!string.IsNullOrEmpty(provider))
{
return RedirectToAction("ExternalLogin", "Account", new
{
provider = provider,
returnUrl = Request.PathBase + Request.Path + Request.QueryString
});
}
return Render(ExternalLoginStatus.Error);
}
// ...
} If you want to avoid the extra hop, you can merge [HttpGet("~/connect/authorize")]
public async Task<IActionResult> Authorize(OpenIdConnectRequest request)
{
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
// If an identity provider was explicitly specified, return a challenge
// to redirect the user to the issuer's remote authorization endpoint.
var provider = (string) request["provider"];
if (!string.IsNullOrEmpty(provider))
{
// Request a redirect to the external login provider.
var returnUrl = Request.PathBase + Request.Path + Request.QueryString;
var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl });
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return Challenge(properties, provider);
}
return Render(ExternalLoginStatus.Error);
}
// ...
} It's important to note that your custom flow has a major vulnerability: it's prone to XSRF attacks as nothing prevents me from redirecting a victim to Consider using an OIDC JS library that takes care of everything for you. E.g https://github.com/manfredsteyer/angular-oauth2-oidc |
@PinpointTownes Thanks for explaining once again. :) Hopefully we'll get there. I have changed the whole flow as you suggested. I have also incorportaed angular-oauth2-oidc for the whole password & implicit flow. Implicit flow branch has these changes. So there aren't any ExternalLogin,ExternalLoginCallback,CreateExternalLoginAccount endpoints anymore. All this work is done in connect/authorize endpoint now. Implicit request is created from js client now for both external login account which don't have local accounts and logging in of external accounts which have accounts already. Both requests are created by angular-oauth2-oidc apis. In short, both flows are triggered using implicit request from js client. On your point regarding XSRF, thanks for pointing out. Just to mention that URL is still save even requests are handled by agular-oauth2-oidc client. Is this fine? Note: I haven't updated the demo site yet as there is a problem with angular-oauth2-oidc i.e password flow is broken now. Even I receive id_token from password flow, Library isn't setting it in session storage. It sets id_token received from implicit flow however. Have raided an issue here. |
@PinpointTownes do you think anything around openidconnect package for this issue? Even I haven't overriden any option for password flow. id_token isn't being set by angular-oauth2-oidc for some reason. It sets the access_token fine. |
As long as the
In the password flow (which is an OAuth2 flow that was not included in OIDC), there was no identity token concept. For convenience and consistency with the other flows, OpenIddict allows returning an identity token in all the flows, including password, client credentials and custom flows (openiddict/openiddict-samples#40 (comment)). Since it's essentially a custom parameter, I'm not surprised it's not natively supported by third-party libs. |
@PinpointTownes Thanks for explaining. I don't understand how can I validate the state is unpredictable? Even browser url has the ticket in it on response, can't think of anything else we could do to avoid that. Now it is handled by js lib, does that make it unpredictable? id_token is quite handy in spa's to display user info (as you have explained in thread you sent). For now I am setting id_token explicitly in password flow. But I am wondering why it stores in implict flow but not password flow. (can that be a bug on the grounds of consistence rather expecting this to be standard in js lib?) |
I thought about this scenarios and came to a conclusion that the simplest way is to open a new window and start implicit flow there. When redirect URI matches your specific URI you get tokens from that window and close it. Using this suggestion you will have an instant redirect to an external provider. |
In Visual studio MVC templates there are examples of social logins. Methods like ExternalLoginCallback (get) and ExternalLoginCallback (post) to register the account with that social login. This relies on Cookies.
In SPA based scenarios how we can achieve social logins relying on typical tokens. If we keep same mechanism to redirect social sites to ExternalLoginCallback (get) callback and load screen to register then how and at what point do we return tokens?
I have an open source project here which work with local logins (email password based logins) but social logins is one missing piece from it. Appreciate any helpful pointers. Thanks
The text was updated successfully, but these errors were encountered: