Closed auxon closed 3 years ago
Zirku: implicit flow demo, with an Aurelia JS application acting as the client and two API projects using introspection (Api1) and local validation (Api2).
https://github.com/openiddict/openiddict-samples#aspnet-core-samples
Thanks for the quick response!
I am having problems because I am trying to get this to work for a .NET Framework 4.6.1 API. I found another older issue with the same problem. https://github.com/openiddict/openiddict-samples/issues/63# I am attempting to do the same thing. Except I'm using Client Credentials flow.
If you need a hand, consider sponsoring the project and I'll make sure to point you in the right direction.
Done!
At first I was doing this from within my API Resource server:
services.AddOpenIddict()
.AddValidation(options =>
{
// Note: the validation handler uses OpenID Connect discovery
// to retrieve the address of the introspection endpoint.
options.SetIssuer("https://localhost:5001/");
options.AddAudiences("SSHandlerServer");
// Configure the validation handler to use introspection and register the client
// credentials used when communicating with the remote introspection endpoint.
options.UseIntrospection()
.SetClientId("SSHandlerServer")
.SetClientSecret("326CACD9-0EEA-4E58-9955-E4C4C3EDB5F5");
// Register the System.Net.Http integration.
options.UseSystemNetHttp();
// Register the Owin host.
options.UseOwin();
});
It sounds like I can't do any of this with Owin, right?
Some problem trying to pay for the sponsorship. Getting a 404 when trying to use PayPal. I made a direct payment instead.
Some problem trying to pay for the sponsorship. Getting a 404 when trying to use PayPal. I made a direct payment instead.
Thanks, much appreciated!
It sounds like I can't do any of this with Owin, right?
It should definitely work with OWIN. Do you use Web API 2 with the OWIN host? If so, did you decorate your API endpoints with [HostAuthentication(OpenIddictValidationOwinDefaults.AuthenticationType)]
? Alternatively (but I wouldn't recommend it), you can call options.UseOwin().UseActiveAuthentication()
to ensure the OpenIddict validation middleware provides an identity for all requests and converts 401 responses to proper challenges.
If it still doesn't work, please post your logs.
Yes, it's Web API 2.2 using Microsoft.AspNet.WebApi 5.2.7 which from what I found means Web API 2. I didn't decorate the API endpoint - I will try that! Thanks!
To be sure I am making sense, I am getting a token from my Authorization server via Postman and then making a Postman request to my .NET Framework 4.6.1 Web API 2 project API. Is Client Credential flow even right in this case?
Without knowing more about your scenario, it's hard to tell. What's sure is that the client credentials flow is suited for machine-to-machine scenarios (i.e when there's no user involved and the client application needs to access API resources on its own).
Ok, that's what I want, M2M. Client credential flow seems correct. I am essentially trying to backport Zirku API 1 to ASP.NET Framework 4.6.1. I want to make sure I have configured things correctly in Startup.cs. I am starting over because my code is now a mess of things I've tried.
Stupid question - how to enable logs? I don't see anything except some debug output.
This is the code for Startup.cs in my Resource Server. The API I am hosting is called "scim".
`using Autofac; using Autofac.Extensions.DependencyInjection; using Autofac.Integration.WebApi; using Microsoft.Extensions.DependencyInjection; using Microsoft.Owin; using OpenIddict.Validation.Owin; using Owin; using Owin.Scim.Extensions; using SSHandlerServer.SCIM.CogniLore; using System; using System.IO; using System.Web.Http;
[assembly: OwinStartup(typeof(SSHandlerServer.Startup))]
namespace SSHandlerServer { public partial class Startup { public void Configuration(IAppBuilder appBuilder) { var container = CreateContainer(appBuilder);
// Register the Autofac scope injector middleware.
appBuilder.UseAutofacLifetimeScopeInjector(container);
// Register the two OpenIddict server/validation middleware.
appBuilder.UseMiddlewareFromContainer<OpenIddictValidationOwinMiddleware>();
appBuilder.Map("/scim", app =>
{
app.UseScimServer(
new Predicate<FileInfo>[] {
fileInfo => fileInfo.Name.Equals("SSHandlerServer.dll", StringComparison.OrdinalIgnoreCase)
},
config =>
{
config.RequireSsl = true; // enable/disable SSL requirements
config.EnableEndpointAuthorization = true; // enable/disable authentication requirements
config.ModifyResourceType<CogniLoreUser>(b => b.SetValidator<CogniLoreUserValidator>());
});
});
var configuration = new HttpConfiguration
{
DependencyResolver = new AutofacWebApiDependencyResolver(container)
};
configuration.MapHttpAttributeRoutes();
// Register the Web API/Autofac integration middleware.
appBuilder.UseAutofacWebApi(configuration);
appBuilder.UseWebApi(configuration);
}
private IContainer CreateContainer(IAppBuilder appBuilder)
{
var services = new ServiceCollection();
// Register the OpenIddict validation components.
services.AddOpenIddict()
.AddValidation(options =>
{
// Note: the validation handler uses OpenID Connect discovery
// to retrieve the address of the introspection endpoint.
options.SetIssuer("https://localhost:5001/");
options.AddAudiences("SSHandlerServer");
// Configure the validation handler to use introspection and register the client
// credentials used when communicating with the remote introspection endpoint.
options.UseIntrospection()
.SetClientId("SSHandlerServer")
.SetClientSecret("326CACD9-0EEA-4E58-9955-E4C4C3EDB5F5");
// Register the System.Net.Http integration.
options.UseSystemNetHttp();
// Register the Owin host.
options.UseOwin().UseActiveAuthentication();
});
var container = services.BuildServiceProvider();
appBuilder.Use(async (context, next) =>
{
var scope = container.CreateScope();
// Store the per-request service provider in the OWIN environment.
context.Set(typeof(IServiceProvider).FullName, scope.ServiceProvider);
try
{
// Invoke the rest of the pipeline.
await next();
}
finally
{
// Remove the scoped service provider from the OWIN environment.
context.Set<IServiceProvider>(typeof(IServiceProvider).FullName, null);
// Dispose of the scoped service provider.
if (scope is IAsyncDisposable disposable)
{
await disposable.DisposeAsync();
}
else
{
scope.Dispose();
}
}
});
appBuilder.UseOpenIddictValidation();
// Create a new Autofac container and import the OpenIddict services.
var builder = new ContainerBuilder();
builder.Populate(services);
return builder.Build();
}
}
}`
I followed the instructions at https://documentation.openiddict.com/guide/getting-started.html to set up an ASP.NET Core Authorization Server I call "IdServer". This is the Startup.cs for IdServer now:
` using IdServer.Data; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.HttpsPolicy; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.UI; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks;
namespace IdServer { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; }
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDbContext<ApplicationDbContext>(options =>
{
// Configure the context to use Microsoft SQL Server.
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
// Register the entity sets needed by OpenIddict.
// Note: use the generic overload if you need
// to replace the default OpenIddict entities.
options.UseOpenIddict();
});
services.AddLogging(logging =>
{
logging.AddDebug();
logging.AddConsole();
});
services.AddOpenIddict()
// Register the OpenIddict core components.
.AddCore(options =>
{
// Configure OpenIddict to use the Entity Framework Core stores and models.
// Note: call ReplaceDefaultEntities() to replace the default entities.
options.UseEntityFrameworkCore()
.UseDbContext<ApplicationDbContext>();
})
// Register the OpenIddict server components.
.AddServer(options =>
{
// Enable the token endpoint.
options.SetTokenEndpointUris("/connect/token");
// Enable the client credentials flow.
options.AllowClientCredentialsFlow();
// Register the signing and encryption credentials.
options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();
// Register Scopes
options.RegisterScopes("scim");
// Register the ASP.NET Core host and configure the ASP.NET Core options.
options.UseAspNetCore()
.EnableTokenEndpointPassthrough();
})
// Register the OpenIddict validation components.
.AddValidation(options =>
{
// Import the configuration from the local OpenIddict server instance.
options.UseLocalServer();
// Register the ASP.NET Core host.
options.UseAspNetCore();
});
// Register the worker responsible of seeding the database with the sample clients.
// Note: in a real world application, this step should be part of a setup script.
services.AddHostedService<Worker>();
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/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.UseCors(builder =>
{
builder.WithOrigins("https://localhost:44353");
builder.WithMethods("GET");
builder.WithHeaders("Authorization");
});
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapDefaultControllerRoute();
endpoints.MapRazorPages();
});
}
}
} `
This is my Worker.cs for the Authorization server:
using IdServer.Data; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using OpenIddict.Abstractions; using System; using System.Threading; using System.Threading.Tasks; using static OpenIddict.Abstractions.OpenIddictConstants;
namespace IdServer {
public class Worker : IHostedService
{
private readonly IServiceProvider _serviceProvider;
public Worker(IServiceProvider serviceProvider)
=> _serviceProvider = serviceProvider;
public async Task StartAsync(CancellationToken cancellationToken)
{
using var scope = _serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
await context.Database.EnsureCreatedAsync();
var manager =
scope.ServiceProvider.GetRequiredService<IOpenIddictApplicationManager>();
if (await manager.FindByClientIdAsync("SSHandlerServer") is null)
{
await manager.CreateAsync(new OpenIddictApplicationDescriptor
{
ClientId = "SSHandlerServer",
ClientSecret = "326CACD9-0EEA-4E58-9955-E4C4C3EDB5F5",
DisplayName = "SSHandlerServer",
Permissions =
{
Permissions.Endpoints.Token,
Permissions.Endpoints.Introspection,
Permissions.GrantTypes.ClientCredentials,
Permissions.Prefixes.Scope + "scim"
}
});
}
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
}
Stupid question - how to enable logs? I don't see anything except some debug output.
OpenIddict uses the .NET Platform Extensions logging stack, so you can use any compatible listener and register it using the regular logging APIs:
E.g with Microsoft.Extensions.Logging.Debug
:
services.AddLogging(options => options.AddDebug());
This is the code for Startup.cs in my Resource Server. The API I am hosting is called "scim".
I wouldn't recommend using both Autofac AND a custom middleware to manually create scopes using the MSFT DI container. I don't think this explains why things are not working (but logging will definitely tell you what's happening) but you'll surely want to remove the custom DI stuff and use Autofac.
Yeah, there is a mix of Autofac because I was trying everything to work. I will clean that up. I set up logging for the Authorization Server (IdServer) and my Resource Server (SSHandlerServer) that hosts the API, thanks for that tip.
I will review your post step-by-step to reconfigure things with AutoFac: https://kevinchalet.com/2020/03/03/adding-openiddict-3-0-to-an-owin-application/
I got logging working so that should help. Thanks.
This is the Resource Server log when I start debugging. It's the only logging I get from the Resource server.
OpenIddict.Validation.OpenIddictValidationDispatcher: Debug: The event OpenIddict.Validation.OpenIddictValidationEvents+ProcessRequestContext was successfully processed by OpenIddict.Validation.Owin.OpenIddictValidationOwinHandlers+InferIssuerFromHost. OpenIddict.Validation.OpenIddictValidationDispatcher: Debug: The event OpenIddict.Validation.OpenIddictValidationEvents+ProcessAuthenticationContext was successfully processed by OpenIddict.Validation.Owin.OpenIddictValidationOwinHandlers+ExtractAccessTokenFromAuthorizationHeader. OpenIddict.Validation.OpenIddictValidationDispatcher: Debug: The event OpenIddict.Validation.OpenIddictValidationEvents+ProcessAuthenticationContext was successfully processed by OpenIddict.Validation.Owin.OpenIddictValidationOwinHandlers+ExtractAccessTokenFromBodyForm. OpenIddict.Validation.OpenIddictValidationDispatcher: Debug: The event OpenIddict.Validation.OpenIddictValidationEvents+ProcessAuthenticationContext was successfully processed by OpenIddict.Validation.Owin.OpenIddictValidationOwinHandlers+ExtractAccessTokenFromQueryString. OpenIddict.Validation.OpenIddictValidationDispatcher: Debug: The event OpenIddict.Validation.OpenIddictValidationEvents+ProcessAuthenticationContext was successfully processed by OpenIddict.Validation.OpenIddictValidationHandlers+ValidateToken. OpenIddict.Validation.OpenIddictValidationDispatcher: Debug: The event OpenIddict.Validation.OpenIddictValidationEvents+ProcessAuthenticationContext was marked as rejected by OpenIddict.Validation.OpenIddictValidationHandlers+ValidateToken. OpenIddict.Validation.OpenIddictValidationDispatcher: Debug: The event OpenIddict.Validation.OpenIddictValidationEvents+ProcessRequestContext was successfully processed by OpenIddict.Validation.Owin.OpenIddictValidationOwinHandlers+InferIssuerFromHost. OpenIddict.Validation.OpenIddictValidationDispatcher: Debug: The event OpenIddict.Validation.OpenIddictValidationEvents+ProcessAuthenticationContext was successfully processed by OpenIddict.Validation.Owin.OpenIddictValidationOwinHandlers+ExtractAccessTokenFromAuthorizationHeader. OpenIddict.Validation.OpenIddictValidationDispatcher: Debug: The event OpenIddict.Validation.OpenIddictValidationEvents+ProcessAuthenticationContext was successfully processed by OpenIddict.Validation.Owin.OpenIddictValidationOwinHandlers+ExtractAccessTokenFromBodyForm. OpenIddict.Validation.OpenIddictValidationDispatcher: Debug: The event OpenIddict.Validation.OpenIddictValidationEvents+ProcessAuthenticationContext was successfully processed by OpenIddict.Validation.Owin.OpenIddictValidationOwinHandlers+ExtractAccessTokenFromQueryString. OpenIddict.Validation.OpenIddictValidationDispatcher: Debug: The event OpenIddict.Validation.OpenIddictValidationEvents+ProcessAuthenticationContext was successfully processed by OpenIddict.Validation.OpenIddictValidationHandlers+ValidateToken. OpenIddict.Validation.OpenIddictValidationDispatcher: Debug: The event OpenIddict.Validation.OpenIddictValidationEvents+ProcessAuthenticationContext was marked as rejected by OpenIddict.Validation.OpenIddictValidationHandlers+ValidateToken. OpenIddict.Validation.OpenIddictValidationDispatcher: Debug: The event OpenIddict.Validation.OpenIddictValidationEvents+ProcessRequestContext was successfully processed by OpenIddict.Validation.Owin.OpenIddictValidationOwinHandlers+InferIssuerFromHost. OpenIddict.Validation.OpenIddictValidationDispatcher: Debug: The event OpenIddict.Validation.OpenIddictValidationEvents+ProcessAuthenticationContext was successfully processed by OpenIddict.Validation.Owin.OpenIddictValidationOwinHandlers+ExtractAccessTokenFromAuthorizationHeader. OpenIddict.Validation.OpenIddictValidationDispatcher: Debug: The event OpenIddict.Validation.OpenIddictValidationEvents+ProcessAuthenticationContext was successfully processed by OpenIddict.Validation.Owin.OpenIddictValidationOwinHandlers+ExtractAccessTokenFromBodyForm. OpenIddict.Validation.OpenIddictValidationDispatcher: Debug: The event OpenIddict.Validation.OpenIddictValidationEvents+ProcessAuthenticationContext was successfully processed by OpenIddict.Validation.Owin.OpenIddictValidationOwinHandlers+ExtractAccessTokenFromQueryString. OpenIddict.Validation.OpenIddictValidationDispatcher: Debug: The event OpenIddict.Validation.OpenIddictValidationEvents+ProcessAuthenticationContext was successfully processed by OpenIddict.Validation.OpenIddictValidationHandlers+ValidateToken. OpenIddict.Validation.OpenIddictValidationDispatcher: Debug: The event OpenIddict.Validation.OpenIddictValidationEvents+ProcessAuthenticationContext was marked as rejected by OpenIddict.Validation.OpenIddictValidationHandlers+ValidateToken.
This is the code for my Resource Server now:
`using Autofac; using Autofac.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Owin; using Owin; using Owin.Scim.Extensions; using System; using System.IO;
[assembly: OwinStartup(typeof(SSHandlerServer.Startup))]
namespace SSHandlerServer
{
public partial class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
appBuilder.Map("/scim", app =>
{
app.UseScimServer(
new Predicate
var services = new ServiceCollection();
services.AddLogging(logging =>
{
logging.AddDebug();
logging.SetMinimumLevel(LogLevel.Trace);
});
// Register the OpenIddict validation components.
services.AddOpenIddict()
.AddValidation(options =>
{
// Note: the validation handler uses OpenID Connect discovery
// to retrieve the address of the introspection endpoint.
options.SetIssuer("https://localhost:5001/");
options.AddAudiences("SSHandlerServer");
// Configure the validation handler to use introspection and register the client
// credentials used when communicating with the remote introspection endpoint.
options.UseIntrospection()
.SetClientId("SSHandlerServer")
.SetClientSecret("326CACD9-0EEA-4E58-9955-E4C4C3EDB5F5");
// Register the System.Net.Http integration.
options.UseSystemNetHttp();
// Register the Owin host.
options.UseOwin().UseActiveAuthentication();
});
var builder = new ContainerBuilder();
builder.Populate(services);
var container = builder.Build();
appBuilder.UseAutofacMiddleware(container);
}
}
}`
It's weird you're not getting more details from the logging stack, like the error/error description returned by OI.
Anyway, the ValidateToken
handler does only one thing: ensuring a token was resolved.
Are you 100% sure you're correctly attaching the token to the HTTP request?
The request looks ok from Postman. I don't get any logging when making my request so something is wrong hooking things up.
I don't get any logging when making my request so something is wrong hooking things up.
OWIN/Katana itself doesn't use the .NET Platform Extensions logging stuff, you need to configure that separately: https://github.com/aspnet/AspNetKatana/wiki/Debugging
Can you try attaching the token to the Authorization
header to see if it makes any difference?
Still the same issue. I tried enabling OWIN/Katana logging verbose. Debugging....
Can you please try with a simple Web API 2 controller like the one in the sample project, just to be sure it's not something affected by SCIM?
Also, try putting the appBuilder.Map("/scim", app => ...)
stuff at the very end of your pipeline (at least, after appBuilder.UseAutofacMiddleware(container);
to ensure OI is always invoked before the SCIM middleware)
That helped! More Logging happening ...
Yes, this is starting to work.
OpenIddict.Server.OpenIddictServerDispatcher: Information: The introspection request was rejected because the application 'SSHandlerServer' was not allowed to use the introspection endpoint. OpenIddict.Server.OpenIddictServerDispatcher: Information: The response was successfully returned as a JSON document: { "error": "unauthorized_client", "error_description": "This client application is not allowed to use the introspection endpoint.", "error_uri": "https://documentation.openiddict.com/errors/ID2075" }.
Ok, so I recreated the application in the database with the right permissions. Now it says
Information: The authentication demand was rejected because the token had no audience attached. I know what to do there I think.
It works!
Wow, thanks! I can't believe this is finally working.
Good to hear (well, read 😄) that! 👏🏻
I am trying to figure out in ASP.NET Framework how to separate the Authorization Server and Resource Server. I want my Authorization Server to run on port https://localhost:5001 and authorize for apis hosted on another port. All the samples demo the resource server and authorization server as one thing. Is this a supported scenario?