Openiddict-core: Simple Password Grant fails after alpha2-0410

Created on 16 Aug 2016  路  7Comments  路  Source: openiddict/openiddict-core

I am able to get a token via password grant flow on version 1.0.0-alpha2-0410 and older, but in any newer versions (NuGet skips forward to 0417) password grant won't give me a token, it 404s.

The only clue I have is that when it was working it complained : AspNet.Security.OpenIdConnect.Server.OpenIdConnectServerMiddleware: Information: No explicit audience was associated with the access token.

With newer versions it doesn't complain about audience but doesn't give me a token either, just 404s. It complains normally about missing fields if I break the request.

This most of my code, it is very simple with nothing custom added in the database (local MS-SQL).

ConfigureServices

            services.AddMvc();

            // Add the database context, defaults to scoped; new context for each request.
            services.AddDbContext<QbDbContext>(
                options => { options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")); });

            // Adds Identity to IoC and confugres with the database.
            services.AddIdentity<QbUser, IdentityRole>(ConfigureIdentityOptions)
                .AddEntityFrameworkStores<QbDbContext>()
                .AddDefaultTokenProviders();

            services.AddOpenIddict<QbUser, QbDbContext>()
                .EnableTokenEndpoint("/api/auth/token") // Password grant route
                .AllowPasswordFlow() // Enables password grant
                .DisableHttpsRequirement() // TODO: Must unset this in production
                .AddEphemeralSigningKey() // TODO: Must unset this in production
                .SetAccessTokenLifetime(TimeSpan.FromDays(365));
        private static void ConfigureIdentityOptions(IdentityOptions options)
        {
            options.SignIn.RequireConfirmedEmail = false;
            options.SignIn.RequireConfirmedPhoneNumber = true;
            options.Password.RequireDigit = false;
            options.Password.RequireLowercase = false;
            options.Password.RequireUppercase = false;
            options.Password.RequireNonAlphanumeric = false;
            options.Password.RequiredLength = 8;
        }

Configure


            app.UseIdentity(); // Authorization using ASP Identity.
            app.UseOAuthValidation();
            app.UseOpenIddict(); // OpenIddict takes care of the token issuing.

            app.UseDefaultFiles();
            app.UseStaticFiles();
            app.UseMvc();
    public class QbUser : OpenIddictUser
    {
    }
    public class QbDbContext : OpenIddictDbContext<QbUser>
    {
        public QbDbContext(DbContextOptions options) : base(options)
        {
        }
    }

Am I doing something obvious wrong or is there a bug?

bug

Most helpful comment

It's definitely an unwanted consequence of the recent design change I mentioned. Looks like we'll have to rework it a bit :smile:

All 7 comments

Thanks for reporting that. I suppose it's related to this recent design change, since it's the only thing that changed recently: https://github.com/openiddict/openiddict-core/pull/190.

Any chance you could share a repro?

I just wrote a repro project from scratch but it worked properly instead, so I'll need to spend some time figuring out exactly where the failure occurs. Maybe I'll solve this myself.

I'll work on this tomorrow. Thanks.

My repro:
https://github.com/t3hmun/repro-openiddict-passwordgrant

The actual problem is that it has started falling through to the app.Run(...) at the end of my Configure() instead of showing the token. If I remove the app.Run(...) it works fine (a good enough work around).

  app.Run(async context =>
  {
      await context.Response.WriteAsync("how is this relevant?");
  });

I was using app.Run(...) as a universal 404 message, that's not a abuse right?

It's definitely an unwanted consequence of the recent design change I mentioned. Looks like we'll have to rework it a bit :smile:

Thanks for posting this issue - I think I get what's happening. The OpenIddict middleware doesn't handle the request for that endpoint immediately - it invokes the next middleware delegate in the pipeline. Your app.Run delegate handles the request by writing to the response. Because the request has been handled, the OpenIddict middleware won't try handling the request "on the way back" in the pipeline either, following this principle from https://docs.asp.net/en/latest/fundamentals/middleware.html:

Avoid modifying HttpResponse after invoking next, one of the next components in the pipeline may have written to the response, causing it to be sent to the client.

Perhaps a good approach to having a default endpoint implementation but allowing developers to overwrite it with their custom endpoint logic would be to create an OpenIddict middleware that only handles token endpoints. It can be called by app.UseOpenIddictEnpointMiddleware or something. Developers should call it after any custom endpoint handling, e.g. after app.UseMvc.

@pholly yup. It's precisely what's happening.

Will be fixed by https://github.com/openiddict/openiddict-core/pull/203. The approach I have in mind is to make having your own "token endpoint action" mandatory to support password token requests. i.e you'll have to include something like that in your project:

[HttpPost("~/connect/token")]
public async Task<IActionResult> Exchange() {
    var request = HttpContext.GetOpenIdConnectRequest();

    if (request.IsPasswordGrantType()) {
        var user = await _userManager.FindByNameAsync(request.Username);
        if (user == null) {
            return Json(new OpenIdConnectResponse {
                Error = OpenIdConnectConstants.Errors.InvalidGrant
            });
        }

        // Ensure the password is valid.
        if (!await _userManager.CheckPasswordAsync(user, request.Password)) {
            if (_userManager.SupportsUserLockout) {
                await _userManager.AccessFailedAsync(user);
            }

            return Json(new OpenIdConnectResponse {
                Error = OpenIdConnectConstants.Errors.InvalidGrant
            });
        }

        if (_userManager.SupportsUserLockout) {
            await _userManager.ResetAccessFailedCountAsync(user);
        }

        var identity = await _userManager.CreateIdentityAsync(user, request.GetScopes());

        // Create a new authentication ticket holding the user identity.
        var ticket = new AuthenticationTicket(
            new ClaimsPrincipal(identity),
            new AuthenticationProperties(),
            OpenIdConnectServerDefaults.AuthenticationScheme);

        ticket.SetResources(request.GetResources());
        ticket.SetScopes(request.GetScopes());

        return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
    }

    return Json(new OpenIdConnectResponse {
        Error = OpenIdConnectConstants.Errors.UnsupportedGrantType
    });
}

(of course, token requests will still be validated by OpenIddict itself before MVC is invoked)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

igorklimenko picture igorklimenko  路  5Comments

xperiandri picture xperiandri  路  4Comments

AlphaCreativeDev picture AlphaCreativeDev  路  3Comments

ivanmariychuk picture ivanmariychuk  路  6Comments

levitatejay picture levitatejay  路  3Comments