简体   繁体   中英

How to integrate Social Login with existing .Net core Web API backend and Angular SPA frontend with working OpenIddict user/password and bearer token

TL;DR

Question: how to implement social login (OAuth2 authorization flow) with an existing SPA/Web API application that is based on identity, user/password, bearer token authentication?


I have an existing application that has:

Backend: .Net Core 2 Web API with Identity and OpenIddict services configured, with a working authentication process based on user/password challenge for bearer token.

Users are stored with Identity (AspNetUsers).

Part of the Startup.cs code

// Register the OpenIddict services.
services.AddOpenIddict()
    .AddCore(options =>
    {
        options.UseEntityFrameworkCore().UseDbContext<ApplicationDbContext>();
    })
    .AddServer(options =>
    {
        options.UseMvc();
        options.EnableTokenEndpoint("/connect/token");
        options.AllowPasswordFlow();
        options.AllowRefreshTokenFlow();
        options.AcceptAnonymousClients();
        options.RegisterScopes(
            OpenIdConnectConstants.Scopes.OpenId,
            OpenIdConnectConstants.Scopes.Email,
            OpenIdConnectConstants.Scopes.Phone,
            OpenIdConnectConstants.Scopes.Profile,
            OpenIdConnectConstants.Scopes.OfflineAccess,
            OpenIddictConstants.Scopes.Roles);
    })
    .AddValidation();

.

Frontend: SPA Angular 7 app that consumes this backend API and token authorization

So basically the current setup is, user inputs user/password to the SPA that invokes the backend /connect/token endpoint that validates the credentials and generates the token for the client.

And now I need to integrate Social Login (OAuth2 Authorization flow) so that

  1. user chooses login with provider,
  2. gets redirected to providers authorization page,
  3. gets redirected back to my application that
  4. needs to create the Identity user and save the Identity UserLoginInfo data and
  5. provide my application token so that the user can login.

I understand the OAuth2 authorization flow that needs to Request an Authorization Code and then Exchange Authorization Code for an Access Token for that provider. I also know that this flow must use backend, once it uses sensitive information like client_secret that can't be stored in client side.

But at some point user needs to interact with frontend, so connecting these parts seems very difficult considering that these are wide used technologies. All practical examples I found on Google were using .Net Core MVC application. I also found this article ASP.NET Core 3.0 Preview 4 - Authentication and authorization for SPAs that seems promising but is still in Preview 4.

I already created the social providers apps and I have client_id, client_secret. Also registered my redirects url's.

What I tried with no success was:

  1. In frontend user chooses login with social provider,
  2. User gets redirected to provider authorization page, authenticates himself and
  3. gets redirected from the provider to my frontend URL ( redirect_uri ) with the provider's code then
  4. my frontend calls my backend /connect/token existing endpoint passing the selected provider and the received code, the endpoint was programmed to receive the provider and code also, then
  5. my backend calls provider's get AccessToken url posting "grant_type", "authorization_code" "code", code "redirect_uri", "https://same_frontend_host/same/path" "client_id", providerClientId "client_secret", providerSecret and receives a StatusCode: 401, ReasonPhrase: 'Unauthorized' response

What am I doing wrong? It's been a real hard time to get this to work.

What worked but it's not what I need

An implicit 2 step authorization flow using frontend for provider authentication calls and a backend call to get my bearer token and create Identity user. With this setup user made a successful login using a social provider, unfortunately it's not what I need

EDIT:

Made a diagram of what is implemented, it is failing at step 5/6 with StatusCode: 401, ReasonPhrase: 'Unauthorized' and further steps are not completed.

在此输入图像描述

The flow you describe pretty much corresponds to "Authorization Cross Domain Code", an OpenID Connect flow that has never been standardized.

I wouldn't recommend going with such a non-standard option. Instead, consider tweaking your flow to make your JS client exclusively communicate with your own authorization server instead of starting the flow by making the client redirect the user agent to an external provider.

The key idea here is that your own authorization server should initiate the initial communication with the external provider (ie it should build the authorization request and redirect your users to the external provider's authorization endpoint) and handle the last part: the callback authorization response. For that, I'd recommend going with the OAuth2/OIDC handlers shipping with ASP.NET Core (there are providers for Google, Facebook and many more)

Of course, this doesn't mean your JS client can't send a hint about the external provider the user should use to authenticate. It's something you can easily handle in your authorization controller. Here's an example:

public class AuthorizationController : Controller
{
    private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider;
    private readonly SignInManager<ApplicationUser> _signInManager;

    public AuthorizationController(
        IAuthenticationSchemeProvider authenticationSchemeProvider,
        SignInManager<ApplicationUser> signInManager)
    {
        _authenticationSchemeProvider = authenticationSchemeProvider;
        _signInManager = signInManager;
    }

    [HttpGet("~/connect/authorize")]
    public async Task<IActionResult> Authorize(OpenIdConnectRequest request)
    {
        Debug.Assert(request.IsAuthorizationRequest(),
            "The OpenIddict binder for ASP.NET Core MVC is not registered. " +
            "Make sure services.AddOpenIddict().AddMvcBinders() is correctly called.");

        if (!User.Identity.IsAuthenticated)
        {
            // Resolve the optional provider name from the authorization request.
            // If no provider is specified, call Challenge() to redirect the user
            // to the login page defined in the ASP.NET Core Identity options.
            var provider = (string) request.GetParameter("identity_provider");
            if (string.IsNullOrEmpty(provider))
            {
                return Challenge();
            }

            // Ensure the specified provider is supported.
            var schemes = await _authenticationSchemeProvider.GetAllSchemesAsync();
            if (!schemes.Any(scheme => scheme.Name == provider))
            {
                return Challenge();
            }

            // When using ASP.NET Core Identity and its default AccountController,
            // the user must be redirected to the ExternalLoginCallback action
            // before being redirected back to the authorization endpoint.
            var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider,
                Url.Action("ExternalLoginCallback", "Account", new
                {
                    ReturnUrl = Request.PathBase + Request.Path + Request.QueryString
                }));

            return Challenge(properties, provider);
        }

        // ...
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM