0

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?

Hesam Kashefi
  • 620
  • 8
  • 15

1 Answers1

2

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.

Kévin Chalet
  • 39,509
  • 7
  • 121
  • 131
  • How and when should I redirect them cause they don't have password and the first thing in token endpoint is to check their grant if it's password or refresh then it's password we check their username and password against database but now instead of password they have a external login record in Database, and what grant should I use? and when WebAuthenticationBroker is closed the cookies are gone so I have to do whatever is needed before it's been closed! please help me it's half a year I'm trying but I've got no where! – Hesam Kashefi Jul 05 '17 at 04:03
  • Take a look at the code flow sample: https://github.com/openiddict/openiddict-samples/blob/master/samples/CodeFlow/AuthorizationServer/Controllers/AuthorizationController.cs – Kévin Chalet Jul 05 '17 at 09:49
  • I saw it (actually it's 6 month that I'm seeing your samples) but I don't get it! how should I Implement this? if I redirect to authorize action I think at least I should have an client_id to send which I don't think fits in my problem cause there is only one client_id and that's what I got from google developer console to authorize google users. if you know how is this please please please make a tutorial on your blog Mr.kevin! I've tried for 6 month or more , but still can't! – Hesam Kashefi Jul 05 '17 at 11:40
  • You're likely trying to make things more complicated than they should be. Your UWP client should be registered directly with your own OpenIddict-based authorization server and not with Google. If you want to delegate authentication to Google, you can do that at the authorization server level, using the Google authentication middleware. That's how the code flow is supposed to work. – Kévin Chalet Jul 05 '17 at 12:16
  • Please correct me i'm wrong, I have an app like Instagram, users register to my application (username/password or through google or Facebook), then they interact with the app, so there is an asp.net core project which uwp app calls for resources and also call it for registration, now when users is registered by user/pass I can go with password grant and get my tokens, but when he registers or logins with google (which i used google middle ware to log them in or register them) i don't know how to get access and refresh token for them from uwp, there is not even one tutorial for this – Hesam Kashefi Jul 05 '17 at 12:27
  • Writing tutorials takes time, and no one volunteered for this task. As I said, in the code flow, that's not how it works. The flow is client -> your authorization server (authorization endpoint/login pages) -> the external provider -> your authorization server -> your client. If you don't like this flow, you can take a look at https://stackoverflow.com/questions/41223694/rich-twitter-digits-google-auth-with-openiddictserver, but it's more risky to implement. – Kévin Chalet Jul 05 '17 at 12:53
  • I do know what is code flow but at the last steps after the user is redirected back to https://localhost:44380/signin-google with accessToken and refreshToken then googleMiddleware uses them and gets user info from google and redirects user to ExternalLoginCallback then user is redirected to ExternalLoginConfirmation when he confirms the user will be registered if he's not already- now in web apps user should be redirected to where he started login but in native apps I thing should get the local token from openIddict but I don't know how?! and it's documented no where and no tutorial for this! – Hesam Kashefi Jul 05 '17 at 15:12
  • `and it's documented no where and no tutorial for this!` > you've already said it 3 times in this thread. Repeating it again and again won't help. ICYMI, OpenIddict is a free OSS project I develop on my spare time and nobody offered to help with documentation. If you need dedicated assistance, I can provide you with consulting services. Otherwise, open a new SO question with all the relevant logs and a network trace and I'll take a look when I can. – Kévin Chalet Jul 05 '17 at 15:18
  • Sorry i didn't mean that it's your fault or anything , I meant it's mainly Microsoft's fault , and others who have done something like this before but didn't teach others how to do it, I appreciate your awesome work on openiddict... – Hesam Kashefi Jul 05 '17 at 15:26
  • Hey, by looking at IdentityServer documentation I just found out what was the problem that kept me unable to implement this all these months! I knew that if I want to sign in users to my API using Google or other providers, I should use Authorization Code flow but I never knew that there will be two Authorization Code flows going on there! first between Client and my API and second between my API and Google. and something else that I found out is that It's not needed to call ExternalLogin action , I just need to use acr_values="idp:Google" to send user immediately to Google. – Hesam Kashefi Jul 10 '17 at 13:28