简体   繁体   中英

OpenIddict - How do you obtain the access token for a user?

I am working on a sample application for OpenIddict using AngularJs. I was told that you shouldnt use clientside frameworks like Satellizer, as this isnt recommended, but instead allow the server to deal with logging in server side (locally and using external login providers), and return the access token.

Well i have a demo angularJs application and uses server side login logic and calls back to the angular app, but my problem is, how do i get the access token for the current user?

here is my startup.cs file, so you can see my configuration so far

public void ConfigureServices(IServiceCollection services) {
    var configuration = new ConfigurationBuilder()
        .AddJsonFile("config.json")
        .AddEnvironmentVariables()
        .Build();

    services.AddMvc();

    services.AddEntityFramework()
        .AddSqlServer()
        .AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(configuration["Data:DefaultConnection:ConnectionString"]));

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders()
        .AddOpenIddict();

    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddTransient<ISmsSender, AuthMessageSender>();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    env.EnvironmentName = "Development";

    var factory = app.ApplicationServices.GetRequiredService<ILoggerFactory>();
    factory.AddConsole();
    factory.AddDebug();

    app.UseDeveloperExceptionPage();

    app.UseIISPlatformHandler(options => {
        options.FlowWindowsAuthentication = false;
    });

    app.UseOverrideHeaders(options => {
        options.ForwardedOptions = ForwardedHeaders.All;
    });

    app.UseStaticFiles();

    // Add a middleware used to validate access
    // tokens and protect the API endpoints.
    app.UseOAuthValidation();

    // comment this out and you get an error saying 
    // InvalidOperationException: No authentication handler is configured to handle the scheme: Microsoft.AspNet.Identity.External
    app.UseIdentity();

    // TOO: Remove
    app.UseGoogleAuthentication(options => {
        options.ClientId = "XXX";
        options.ClientSecret = "XXX";
    });

    app.UseTwitterAuthentication(options => {
        options.ConsumerKey = "XXX";
        options.ConsumerSecret = "XXX";
    });

    // Note: OpenIddict must be added after
    // ASP.NET Identity and the external providers.
    app.UseOpenIddict(options =>
    {
        options.Options.AllowInsecureHttp = true;
        options.Options.UseJwtTokens();
    });

    app.UseMvcWithDefaultRoute();

    using (var context = app.ApplicationServices.GetRequiredService<ApplicationDbContext>()) {
        context.Database.EnsureCreated();

        // Add Mvc.Client to the known applications.
        if (!context.Applications.Any()) {
            context.Applications.Add(new Application {
                Id = "myClient",
                DisplayName = "My client application",
                RedirectUri = "http://localhost:5000/signin",
                LogoutRedirectUri = "http://localhost:5000/",
                Secret = Crypto.HashPassword("secret_secret_secret"),
                Type = OpenIddictConstants.ApplicationTypes.Confidential
            });

            context.SaveChanges();
        }
    }
}

Now my AccountController is basically the same as the normal Account Controller, although once users have logged in (using local and external signin) i use this function and need an accessToken.

private IActionResult RedirectToAngular()
{
    // I need the accessToken here

    return RedirectToAction(nameof(AccountController.Angular), new { accessToken = token });
}

As you can see from the ExternalLoginCallback method on the AccountController

public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null)
{
    var info = await _signInManager.GetExternalLoginInfoAsync();
    if (info == null)
    {
        return RedirectToAction(nameof(Login));
    }

    // Sign in the user with this external login provider if the user already has a login.
    var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
    if (result.Succeeded)
    {
        // SHOULDNT THE USER HAVE A LOCAL ACCESS TOKEN NOW??
        return RedirectToAngular();
    }
    if (result.RequiresTwoFactor)
    {
        return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl });
    }
    if (result.IsLockedOut)
    {
        return View("Lockout");
    }
    else {
        // If the user does not have an account, then ask the user to create an account.
        ViewData["ReturnUrl"] = returnUrl;
        ViewData["LoginProvider"] = info.LoginProvider;
        var email = info.ExternalPrincipal.FindFirstValue(ClaimTypes.Email);
        return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = email });
    }
}
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
if (result.Succeeded)
{
    // SHOULDNT THE USER HAVE A LOCAL ACCESS TOKEN NOW??
    return RedirectToAngular();
}

That's not how it's supposed to work. Here's what happens in the classical flow:

  • The OAuth2/OpenID Connect client application (in your case, your Satellizer JS app) redirects the user agent to the authorization endpoint ( /connect/authorize by default in OpenIddict) with all the mandatory parameters: client_id , redirect_uri (mandatory in OpenID Connect), response_type and nonce when using the implicit flow (ie response_type=id_token token ). Satellizer should do that for you, assuming you've correctly registered your authorization server (1).

  • If the user is not already logged in, the authorization endpoint redirects the user to the login endpoint (in OpenIddict, it's done for you by an internal controller). At this point, your AccountController.Login action is invoked and the user is displayed a login form.

  • When the user is logged in (after a registration process and/or an external authentication association), he/she MUST be redirected back to the authorization endpoint: you can't redirect the user agent to your Angular app at this stage . Undo the changes made to ExternalLoginCallback and it should work.

  • Then, the user is displayed a consent form indicating he/she's about to allow your JS app to access his personal data on his/her behalf. When the user submits the consent form, the request is handled by OpenIddict, an access token is generated and the user agent is redirected back to the JS client app, with the token appended to the URI fragment.

[1]: according to the Satellizer documentation, it should be something like that:

$authProvider.oauth2({
    name: 'openiddict',
    clientId: 'myClient',
    redirectUri: window.location.origin + '/done',
    authorizationEndpoint: window.location.origin + '/connect/authorize',
    responseType: 'id_token token',
    scope: ['openid'],
    requiredUrlParams: ['scope', 'nonce'],
    nonce: function() { return "TODO: implement appropriate nonce generation and validation"; },
    popupOptions: { width: 1028, height: 529 }
});

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