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)

I get a 404 error message

Even if the backend was able to successfully validate the credentials

Can someone point in the correct direction? I have been pulling my hair out the entire day.
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()
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
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:
Enable the required middlewares and able the controller route.
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
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;
});
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With