I've built an OpenIddict OAuth server using the marvelous guide by Robin van der Knaap although I've actually implemented it using Identity instead of cookies.
I'm also trying to run a Web API from the same project because the end customer only wants a single system to call.
At the moment I'm doing all my endpoint testing in postman.
This user info endpoint in the AuthorisationController works fine:
[Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)]
[HttpGet("~/connect/userinfo")]
public async Task<IActionResult> Userinfo()
{
var claimsPrincipal = (await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)).Principal;
return Ok(new
{
Name = claimsPrincipal.GetClaim(OpenIddictConstants.Claims.Subject),
Occupation = "Developer",
Age = 43
});
}
But when I try to call this custom Web API controller endpoint (https://<domain.com>/api/Test/):
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
[HttpGet]
[Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)]
public IActionResult Index()
{
return Ok("hello");
}
}
I just get the following error:
System.InvalidOperationException: An identity cannot be extracted from this request. This generally indicates that the OpenIddict server stack was asked to validate a token for an endpoint it doesn't manage. To validate tokens received by custom API endpoints, the OpenIddict validation handler (e.g OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme or OpenIddictValidationOwinDefaults.AuthenticationType) must be used instead. at OpenIddict.Server.OpenIddictServerHandlers.ValidateAuthenticationDemand.HandleAsync(ProcessAuthenticationContext context) at OpenIddict.Server.OpenIddictServerDispatcher.DispatchAsync[TContext](TContext context) at OpenIddict.Server.OpenIddictServerDispatcher.DispatchAsync[TContext](TContext context) at OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandler.HandleAuthenticateAsync() at Microsoft.AspNetCore.Authentication.AuthenticationHandler`1.AuthenticateAsync() at Microsoft.AspNetCore.Authentication.AuthenticationService.AuthenticateAsync(HttpContext context, String scheme) at Microsoft.AspNetCore.Authorization.Policy.PolicyEvaluator.AuthenticateAsync(AuthorizationPolicy policy, HttpContext context) at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
My Program.cs looks like this:
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using OAuthServer;
using OAuthServer.Data;
using OpenIddict.Server.AspNetCore;
using OpenIddict.Validation.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseSqlServer(connectionString);
// Register the entity sets needed by OpenIddict.
options.UseOpenIddict();
});
builder.Services.AddOpenIddict()
// Register the OpenIddict core components.
.AddCore(options =>
{
// Configure OpenIddict to use the EF Core stores/models.
options.UseEntityFrameworkCore()
.UseDbContext<ApplicationDbContext>();
})
// Register the OpenIddict server components.
.AddServer(options =>
{
options
.AllowClientCredentialsFlow()
.AllowAuthorizationCodeFlow().RequireProofKeyForCodeExchange()
.AllowRefreshTokenFlow();
options
.SetTokenEndpointUris("/connect/token")
.SetAuthorizationEndpointUris("/connect/authorize")
.SetTokenEndpointUris("/connect/token")
.SetUserinfoEndpointUris("/connect/userinfo");
// Encryption and signing of tokens TODO: Replace with x.509 cert
options
.AddEncryptionCertificate(CertificateHelper.LoadCertificateFromKeyVault(builder.Configuration["KeyVault:Name"], builder.Configuration["OAuth:EncryptionCertName"]))
.AddSigningCertificate(CertificateHelper.LoadCertificateFromKeyVault(builder.Configuration["KeyVault:Name"], builder.Configuration["OAuth:EncryptionCertName"]))
/*.AddEphemeralEncryptionKey()
.AddEphemeralSigningKey()*/
.DisableAccessTokenEncryption();
// Register scopes (permissions)
options.RegisterScopes("api");
// Register the ASP.NET Core host and configure the ASP.NET Core-specific options.
options
.UseAspNetCore()
.EnableTokenEndpointPassthrough()
.EnableAuthorizationEndpointPassthrough()
.EnableUserinfoEndpointPassthrough();
})
.AddValidation();
builder.Services.AddAuthentication(options => options.DefaultScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
builder.Services.AddHostedService<TestData>();
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
builder.Services.AddControllers();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
});
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();
app.Run();
How can I make [Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)]
work on custom API endpoints?
UPDATE
I have a feeling the Web API bit is a red-herring and the issue is something more fundamental. I tried to add an MVC action in the same AuthorisationController:
[Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)]
[HttpGet("~/connect/hello")]
public async Task<IActionResult> Hello()
{
return Ok("hi");
}
But that gives the same error.
I think I should probably just be using [Authorize] without specifying the scheme (which is what I was originally trying to do) but that always gives unauthorised.....
I suspect this article has something in it I need, but it's for an old version of OpenIddict (I'm using 3.1.1) and I can't figure out the current behaviour.