Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting a 404 Not found for OpenIddict even if "The token request was successfully validated."

I am attempting to use OpenIddict to set up a client credentials flow using

services.AddDbContext<MyDbContext>(options =>
{
    options.UseOpenIddict();
}

services.AddOpenIddict()
        .AddCore(options =>
        {
           options.UseEntityFrameworkCore()
                  .UseDbContext<MyDbContext>();
        })
        .AddServer(options =>
        {
           options
               .SetTokenEndpointUris("/connect/token");

           options
               .AllowClientCredentialsFlow();

           options
               .AddDevelopmentEncryptionCertificate()
               .AddDevelopmentSigningCertificate();

           options
               .UseAspNetCore()
           .EnableTokenEndpointPassthrough();
        });

services.AddAuthentication(options =>
   {
       options.DefaultAuthenticateScheme = "Identity.Application";
       options.DefaultChallengeScheme = "Identity.Application";
       options.DefaultSignInScheme = "Identity.Application";
   });

services.AddAuthentication();
services.AddAuthorization();
services.AddRouting();
services.AddControllers();

When I invoke the token endpoint /connect/token with the following client credentials (which are correct)

Screenshot of Postman

I get a 404 error message

404 error code

Even if the backend was able to successfully validate the credentials

enter image description here

Can someone point in the correct direction? I have been pulling my hair out the entire day.

like image 611
Anish Avatar asked Feb 01 '26 07:02

Anish


1 Answers

To answer this, you first needs to understand what is pass-through mode when you enabled it for the /token endpoint when you call .EnableTokenEndpointPassthrough()

Pass-Through Mode

  • Request Validation: OpenIddict handles validation automatically.

  • Token Generation: The developer must implement logic to generate the token.

  • Response Handling: The developer must implement logic to return the response.

  • Controller Required: Yes

Event Mode

  • Request Validation: OpenIddict handles validation automatically

  • Token Generation: Developers can customize token generation by handling events like [HandleTokenRequestContext](vscode-file://vscode-app/c:/Users/qphan/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html) or ProcessSignInContext.

  • Response Handling: OpenIddict generates and sends the response automatically unless overridden by the developer in an event handler

  • Controller Required: No

With the way your code enable pass-through mode, you need to do two things:

  1. Enable the required middlewares and able the controller route.

    app.UseAuthentication();
    app.UseAuthorization();
    app.MapControllers();
    
  2. Implement your auth controller for the token endpoint, here one I use to test out this answer:

    [ApiController]
    [Route("connect")]
    public class AuthorizationController: ControllerBase
    {
     private readonly IOpenIddictApplicationManager _applicationManager;
    
     public AuthorizationController(IOpenIddictApplicationManager applicationManager)
         => _applicationManager = applicationManager;
    
     [HttpGet]
     public IActionResult Authorize()
     {
         // Handle authorization request
         return Ok("Authorization request handled.");
     }
    
     [HttpPost]
     [Route("token")]
     public async Task<IActionResult> Token()
     {
         var request = HttpContext.GetOpenIddictServerRequest() ?? throw new InvalidOperationException("The OpenIddict server request cannot be retrieved.");
    
         if (request.IsClientCredentialsGrantType())
         {
             // Note: the client credentials are automatically validated by OpenIddict:
             // if client_id or client_secret are invalid, this action won't be invoked.
    
             var application = await _applicationManager.FindByClientIdAsync(request.ClientId) ??
                 throw new InvalidOperationException("The application cannot be found.");
    
             // Create a new ClaimsIdentity containing the claims that
             // will be used to create an id_token, a token or a code.
             var identity = new ClaimsIdentity(TokenValidationParameters.DefaultAuthenticationType, Claims.Name, Claims.Role);
    
             // Use the client_id as the subject identifier.
             identity.SetClaim(Claims.Subject, await _applicationManager.GetClientIdAsync(application));
             identity.SetClaim(Claims.Name, await _applicationManager.GetDisplayNameAsync(application));
    
             identity.SetDestinations(static claim => claim.Type switch
             {
                 // Allow the "name" claim to be stored in both the access and identity tokens
                 // when the "profile" scope was granted (by calling principal.SetScopes(...)).
                 Claims.Name when claim.Subject.HasScope(Scopes.Profile)
                     => [Destinations.AccessToken, Destinations.IdentityToken],
    
                 // Otherwise, only store the claim in the access tokens.
                 _ => [Destinations.AccessToken]
             });
    
             return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
         }
    
         throw new NotImplementedException("The specified grant is not implemented.");
     }
    }
    

Now, let's say you don't want to enable the pass-through mode by not having the line EnableTokenEndpointPassthrough() then OpenIddict will use its events model to handle the request.

In such as case you can easily extend certain event logic to introduce some manual steps you wish

For example:

options.AddEventHandler<OpenIddictServerEvents.HandleTokenRequestContext>(builder =>
{
    builder.UseInlineHandler(context =>
    {
        // Custom logic to handle the token request
        context.Principal = new ClaimsPrincipal(new ClaimsIdentity(new[]
        {
            new Claim(Claims.Subject, "custom_user_id"),
            new Claim(Claims.Name, "Custom User")
        }, "Bearer"));

        return default;
    });
});
like image 118
Fylix Avatar answered Feb 03 '26 19:02

Fylix