asp.net-coreopenidopenid-connectopeniddictgoogle-openidconnect

How to Generate AccessToken for user who is logged in with External Providers


I have an API implemented by asp.net core. I've used OpenIddict to generate access token and refresh token for users who registered to my api by email and password. I've added Google middleware (.UseGoogleAuthentication ... ) to my API and I can successfully log in user with Google. My client is UWP and I use WebAuthenticationBroker to get redirected to google after sending a reuest to localhost/Account/ExternalLogin/Google. when the users is logged In with google he is redirected to Account/ExternalLoginConfirmation which is trivial to this point now before it finishes with ExternalLoginConfirmation I Want to generate and send back an Access Token and a refresh token for the user cause if the WebAuthenticationBroker get's closed I have no other way to get tokens for this user(cause he has no password and the username will be unknown to me). Itried this :

//
    // POST: /Account/ 
    [HttpPost("ExternalLoginConfirmation")]
    [AllowAnonymous]
    //[ValidateAntiForgeryToken]
    public async Task<IActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model,
        string returnUrl = null)
    {
        if (ModelState.IsValid)
        {
            // Get the information about the user from the external login provider
            var info = await SignInManager.GetExternalLoginInfoAsync();
            if (info == null)
                return View("ExternalLoginFailure");
            var user = new UserInfo { UserName = model.Email, Email = model.Email };
            var result = await UserManager.CreateAsync(user);
            if (result.Succeeded)
            {
                result = await UserManager.AddLoginAsync(user, info);
                if (result.Succeeded)
                {
                    await SignInManager.SignInAsync(user, false);
                    Logger.LogInformation(6, "User created an account using {Name} provider.", info.LoginProvider);
                    var identity = new ClaimsIdentity(
                OpenIdConnectServerDefaults.AuthenticationScheme,
                OpenIdConnectConstants.Claims.Name, null);

                    // Add a "sub" claim containing the user identifier, and attach
                    // the "access_token" destination to allow OpenIddict to store it
                    // in the access token, so it can be retrieved from your controllers.
                    identity.AddClaim(OpenIdConnectConstants.Claims.Subject,
                        user.Id,
                        OpenIdConnectConstants.Destinations.AccessToken);

                    identity.AddClaim(OpenIdConnectConstants.Claims.Name, user.UserName,
                        OpenIdConnectConstants.Destinations.AccessToken);

                    // ... add other claims, if necessary.

                    var principal = new ClaimsPrincipal(identity);

                    var authenticateInfo = await HttpContext.Authentication.GetAuthenticateInfoAsync(info.LoginProvider);
                    var ticket = CreateTicketAsync(principal, authenticateInfo.Properties);

                    // Ask OpenIddict to generate a new token and return an OAuth2 token response.
                    return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
                    //return RedirectToLocal(returnUrl);
                }
            }
            AddErrors(result);
        }

        //ViewData["ReturnUrl"] = returnUrl;
        return BadRequest();
    }

    #region _helpers

    private AuthenticationTicket CreateTicketAsync(ClaimsPrincipal principal,
        AuthenticationProperties properties = null)
    {
        // Create a new authentication ticket holding the user identity.
        var ticket = new AuthenticationTicket(principal, properties,
            OpenIdConnectServerDefaults.AuthenticationScheme);

       ticket.SetScopes(new[]
       {
           /* openid: */ OpenIdConnectConstants.Scopes.OpenId,
           /* email: */ OpenIdConnectConstants.Scopes.Email,
           /* profile: */ OpenIdConnectConstants.Scopes.Profile,
           /* offline_access: */ OpenIdConnectConstants.Scopes.OfflineAccess,
           /* roles: */ OpenIddictConstants.Scopes.Roles
       });

        ticket.SetAudiences(Configuration["Authentication:OpenIddict:Audience"]);

        return ticket;
    }

    private void AddErrors(IdentityResult result)
    {
        foreach (var error in result.Errors)
            ModelState.AddModelError(string.Empty, error.Description);
    }

    private IActionResult RedirectToLocal(string returnUrl)
    {
        if (Url.IsLocalUrl(returnUrl))
            return Redirect(returnUrl);
        return BadRequest();
    }

    #endregion

but this fails and throws exception : an authorization or token response cannot be returned from this controller

now how do I generate these Tokens for the user?


Solution

  • now how do I generate these Tokens for the user?

    OpenIddict deliberately prevents you from returning OIDC responses from non-OIDC endpoints (for obvious security reasons).

    To make your scenario work, you must redirect your users back to the authorization endpoint with all the OpenID Connect parameters.

    Concretely, you should revert all the changes added to ExternalLoginConfirmation() (that should return RedirectToLocal(returnUrl);) and move the SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme); part back to your authorization controller.