question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Setting up custom scopes (The specified scope type is not compatible with the Entity Framework Core stores)

See original GitHub issue

Confirm you’ve already contributed to this project or that you sponsor it

  • I confirm I’m a sponsor or a contributor

Version

3.x

Question

Hey Kevin, great initiative with openiddict, I love the framework and I have been trying to wrap my head around some of the features. I have been through most of the samples and I have been through “all” relevant issues to my problem and of course StackOverflow. Unfortunately, I cant seem to find a good tutorial, readme, or answer to my issues.

My goal is to create a centralized authentication server where I can allow certain clients to ask for permissions for certain audiences. E.g.

  • client1 can ask for auth for resource server A
  • client2 can ask for auth for resource server A & resource server B
  • client 3 can ask for auth for resource server C

to my knowledge:

  • we use audience to differentiate these resource servers(?)
  • we can also also create custom scopes for custom resource servers
    • e.g. we can say scope ‘scopeA’ is only valid for ‘resource server B’

1. how do i set an audience for a client? looking at the /userinfo it seems as if clientid == audience, is this correct?


2. How to create custom scopes? What i’ve found is required to setup custom scopes is

  1. register opendict options.UseOpenIddict<CustomApplication, CustomAuthorization, CustomScope, CustomToken, long>();
  2. replace default entities options.UseEntityFrameworkCore().UseDbContext<MyDbContext>().ReplaceDefaultEntities<CustomApplication, CustomAuthorization, CustomScope, CustomToken, long>();
  3. register scope: options.RegisterScopes(Scopes.Email, Scopes.Profile, Scopes.Roles, Scopes.OfflineAccess, Scopes.OpenId, "custom_scope_example");
  4. create models that inherit from their respectable parents (maybe step 1): documentation
  5. Register the models as a DbSet
        public DbSet<CustomApplication> Application { get; set; }
        public DbSet<CustomAuthorization> Authorization { get; set; }
        public DbSet<CustomScope> Scope{ get; set; }
        public DbSet<CustomToken> Token { get; set; }
       public DbSet<OpenIddictEntityFrameworkCoreScope> OpenIddictEntityFrameworkCoreScope { get; set; } 
       //Why do I have to add OpenIddictEntityFrameworkCoreScope? Is this documented anywhere?

if all that goes well and I get my custom_scope_example into the database I get another error (which is the one I’ve been really struggling with):

Postman call: (scope is irrelevant here, same error comes regardless) image image

System.InvalidOperationException: The specified scope type is not compatible with the Entity Framework Core stores.
When enabling the Entity Framework Core stores, make sure you use the built-in 'OpenIddictEntityFrameworkCoreScope' entity or a custom entity that inherits from the generic 'OpenIddictEntityFrameworkCoreScope' entity.
   at OpenIddict.EntityFrameworkCore.OpenIddictEntityFrameworkCoreScopeStoreResolver.<Get>b__4_0[TScope](Type key)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at OpenIddict.EntityFrameworkCore.OpenIddictEntityFrameworkCoreScopeStoreResolver.Get[TScope]()
   at OpenIddict.Core.OpenIddictScopeCache`1..ctor(IOptionsMonitor`1 options, IOpenIddictScopeStoreResolver resolver)
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.ConstructorInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
   at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)
   at lambda_method229(Closure, IServiceProvider, Object[])
   at Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider.<>c__DisplayClass6_0.<CreateControllerFactory>g__CreateController|0(ControllerContext controllerContext)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Elmah.Io.AspNetCore.ElmahIoMiddleware.Invoke(HttpContext context) in /_/src/Elmah.Io.AspNetCore/ElmahIoMiddleware.cs:line 40
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

If anyone needs to see more code:

program.cs

using FaID.Context;
using FaID.Models;
using FaID.Services.AuthorizationServer;
using FaID.Services.Data;
using FaID.Settings;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Abstractions;
using System.Data;
using System.Security.Cryptography.X509Certificates;
using static OpenIddict.Abstractions.OpenIddictConstants;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddDbContext<FaDbContext>(opt =>
{
    opt.UseNpgsql(builder.Configuration.GetConnectionString("dev"));
    opt.UseOpenIddict<CustomApplication, CustomAuthorization, CustomScope, CustomToken, long>();
});

builder.Services.Configure<IdentityOptions>(options =>
{
    options.ClaimsIdentity.UserNameClaimType = OpenIddictConstants.Claims.Name;
    options.ClaimsIdentity.UserIdClaimType = OpenIddictConstants.Claims.Subject;
    options.ClaimsIdentity.RoleClaimType = OpenIddictConstants.Claims.Role;
});

builder.Services.AddOpenIddict().AddCore(options =>
{
    options.UseEntityFrameworkCore().UseDbContext<FaDbContext>().ReplaceDefaultEntities<CustomApplication, CustomAuthorization, CustomScope, CustomToken, long>();
})
    .AddServer(options =>
    {
        options
            .AllowClientCredentialsFlow()
            .AllowPasswordFlow()
            .AllowRefreshTokenFlow();

        options.RegisterScopes(Scopes.Email, Scopes.Profile, Scopes.Roles, Scopes.OfflineAccess, Scopes.OpenId, "faid_client_scope");
        options.SetTokenEndpointUris("/token")
        .SetAuthorizationEndpointUris("/connect/authorize")
        .SetUserinfoEndpointUris("/connect/userinfo")
        .SetVerificationEndpointUris("/connect/verify");


        //temp code
        X509Certificate2 privateKey;
        var bytes = File.ReadAllBytes(builder.Configuration["Auth:PrivateKeyPath"] ?? "");
        privateKey = new X509Certificate2(bytes, "password");
        X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
        store.Open(OpenFlags.ReadOnly);

        X509Certificate2 encryptionKey;
        var bytes2 = File.ReadAllBytes(builder.Configuration["Auth:EncryptionkeyPath"] ?? "");
        encryptionKey = new X509Certificate2(bytes2, "password");
        X509Store store2 = new X509Store(StoreName.My, StoreLocation.LocalMachine);
        store.Open(OpenFlags.ReadOnly);

        options
        .AddSigningCertificate(privateKey)
        .AddEncryptionCertificate(encryptionKey).DisableAccessTokenEncryption();

        options.SetAccessTokenLifetime(TimeSpan.FromMinutes(60));
        options.SetRefreshTokenLifetime(TimeSpan.FromDays(60));

        options
                .UseAspNetCore()
                .EnableTokenEndpointPassthrough()
                .EnableAuthorizationEndpointPassthrough();
    }
).AddValidation(options =>
{
    options.AddAudiences("faid_client");
    options.UseLocalServer();
    options.UseAspNetCore();
});

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = OpenIddictConstants.Schemes.Bearer;
    options.DefaultChallengeScheme = OpenIddictConstants.Schemes.Bearer;
});

builder.Services.AddAuthorization(options =>
{
   options.AddPolicy("faid_client", builder =>
   {
       builder.RequireAuthenticatedUser();
       builder.RequireAssertion(context => context.User.HasScope("faid_client_scope"));
   });

//Add openiddict client
builder.Services.AddHostedService<TestData>();

builder.Services.AddIdentity<User, IdentityRole>()
    .AddSignInManager()
    .AddEntityFrameworkStores<FaDbContext>()
    .AddDefaultTokenProviders();


builder.Services.Configure<IdentityOptions>(o =>
{
    // Password settings.
    o.Password.RequiredUniqueChars = 0;
    o.Password.RequiredLength = 1;
    o.Password.RequireNonAlphanumeric = false;
    o.Password.RequireDigit = false;
    o.Password.RequireUppercase = false;

    // User settings.
    o.User.AllowedUserNameCharacters =
    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-.@";
    o.User.RequireUniqueEmail = false;
});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.UseCors(o => {
    o.AllowAnyHeader();
    o.AllowAnyMethod();
    o.SetIsOriginAllowedToAllowWildcardSubdomains().WithOrigins("http://localhost:3000/", "\"http://localhost:3000/\"", "http://localhost:3000", "http://localhost:3000/*", "http://localhost:3000/signin");
    o.AllowCredentials();
});
app.UseHttpsRedirection();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.UseElmahIo();

app.Run();

testdata.cs (client & custom scope)

using FaID.Context;
using FaID.Models;
using Microsoft.EntityFrameworkCore;
using OpenIddict.Abstractions;
using OpenIddict.Core;
using OpenIddict.EntityFrameworkCore.Models;
using static OpenIddict.Abstractions.OpenIddictConstants;

namespace FaID.Services.AuthorizationServer
{
    public class TestData : IHostedService
    {
        private readonly IServiceProvider _serviceProvider;

        public TestData(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }

        public async Task StartAsync(CancellationToken cancellationToken)
        {
            using var scope = _serviceProvider.CreateScope();

            var context = scope.ServiceProvider.GetRequiredService<FaDbContext>();
            await context.Database.EnsureCreatedAsync(cancellationToken);

            var manager = scope.ServiceProvider.GetRequiredService<IOpenIddictApplicationManager>();
            var client = await manager.FindByClientIdAsync("postman", cancellationToken);
            if (client is null)
            {
                await manager.CreateAsync(new OpenIddictApplicationDescriptor
                {
                    ClientId = "postman",
                    ClientSecret = "postman-secret",
                    DisplayName = "Postman Client",
                    RedirectUris = { new Uri("https://oauth.pstmn.io/v1/callback") },
                    Permissions =
                    {
                        OpenIddictConstants.Permissions.Endpoints.Authorization,
                        OpenIddictConstants.Permissions.Endpoints.Token,

                        OpenIddictConstants.Permissions.GrantTypes.RefreshToken,
                        OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode,
                        OpenIddictConstants.Permissions.GrantTypes.ClientCredentials,
                        OpenIddictConstants.Permissions.GrantTypes.Password,

                        OpenIddictConstants.Permissions.Scopes.Email,
                        OpenIddictConstants.Permissions.Scopes.Roles,
                        OpenIddictConstants.Permissions.Scopes.Address,
                        OpenIddictConstants.Permissions.Scopes.Phone,
                        OpenIddictConstants.Permissions.Prefixes.Scope + "faid_client_scope",

                        OpenIddictConstants.Permissions.ResponseTypes.Code,
                        OpenIddictConstants.Permissions.ResponseTypes.IdToken,
                        OpenIddictConstants.Permissions.ResponseTypes.CodeIdToken,
                        OpenIddictConstants.Permissions.ResponseTypes.Token
                    },

                }, cancellationToken);
            }
            var scopeManager = scope.ServiceProvider.GetRequiredService<OpenIddictScopeManager<OpenIddictEntityFrameworkCoreScope>>();
            if (await scopeManager.FindByNameAsync("faid_client_scope") == null)
                await scopeManager.CreateAsync(new OpenIddictScopeDescriptor
                {
                    DisplayName = "FAICS",
                    Name = "faid_client_scope",
                    Resources = { "faid_client" }
                }, cancellationToken);
        }

        public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
    }
}

FaDbContext.cs

    public class FaDbContext : IdentityDbContext<User>
    {
        public DbSet<User> Users { get; set; }
        public DbSet<CustomApplication> Application { get; set; }
        public DbSet<CustomAuthorization> Authorization { get; set; }
        public DbSet<CustomScope> Scope{ get; set; }
        public DbSet<CustomToken> Token { get; set; }
        public DbSet<OpenIddictEntityFrameworkCoreScope> OpenIddictEntityFrameworkCoreScope { get; set; }

        public FaDbContext(DbContextOptions<FaDbContext> options) : base(options) { }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
        }
    }

openiddictentities.cs (mainly for testing purposes) Do i need this class just to create custom scopes with audience?

    public class CustomApplication : OpenIddictEntityFrameworkCoreApplication<long, CustomAuthorization, CustomToken> {public string CustomProperty { get; set; } = "";}
    public class CustomAuthorization : OpenIddictEntityFrameworkCoreAuthorization<long, CustomApplication, CustomToken> {public string CustomProperty { get; set; } = "";}
    public class CustomScope : OpenIddictEntityFrameworkCoreScope<long>{ public string CustomProperty { get; set; } = "";}
    public class CustomToken : OpenIddictEntityFrameworkCoreToken<long, CustomApplication, CustomAuthorization>{public string CustomProperty { get; set; } = "";}

database

tables:

image

OpenIddictEntityFrameworkCoreScope table:

image

“5897161b-3d85-401f-a144-2e75ea00a4fb” “5a3c21bb-86b1-4a43-ab80-eacef19cd4c9” “FAICS” “faid_client_scope” “[”“faid_client”“]” “[”“ept:authorization”“,”“ept:token”“,”“gt:refresh_token”“,”“gt:authorization_code”“,”“gt:client_credentials”“,”“gt:password”“,”“scp:email”“,”“scp:roles”“,”“scp:address”“,”“scp:phone”“,”“scp:faid_client_scope”“,”“rst:code”“,”“rst:id_token”“,”“rst:code id_token”“,”“rst:token”“]” “[”“https://oauth.pstmn.io/v1/callback”“]” “confidential”

OpeniddictScopes table (empty):

image

OpenIddictApplications:

1 “postman” “AQAAAAEAACcQAAAAEEpBV+j+D0E18NMbc64js7A65HArrCVdiAXUxfqm+TsapbG+AytKwmUHi9bGFgi1pg==” “b77de4d4-5634-4e7a-a707-59acdb71eb6c” “Postman Client” “[”“ept:authorization”“,”“ept:token”“,”“gt:refresh_token”“,”“gt:authorization_code”“,”“gt:client_credentials”“,”“gt:password”“,”“scp:email”“,”“scp:roles”“,”“scp:address”“,”“scp:phone”“,”“scp:faid_client_scope”“,”“rst:code”“,”“rst:id_token”“,”“rst:code id_token”“,”“rst:token”“]” “[”“https://oauth.pstmn.io/v1/callback”“]” “confidential”

Id: 1 CustomProperty: “” ClientId: “postman” ClientSecret: “AQAAAAEAACcQAAAAEEpBV+j+D0E18NMbc64js7A65HArrCVdiAXUxfqm+TsapbG+AytKwmUHi9bGFgi1pg==” ConcurrencyToken: “b77de4d4-5634-4e7a-a707-59acdb71eb6c” ConsentType: null Display Name: “Postman Client” Display Names: null Permissions: “[""ept:authorization"",""ept:token"",""gt:refresh_token"",""gt:authorization_code"",""gt:client_credentials"",""gt:password"",""scp:email"",""scp:roles"",""scp:address"",""scp:phone"",""scp:faid_client_scope"",""rst:code"",""rst:id_token"",""rst:code id_token"",""rst:token""]" PostLogoutRedirectUris: null RedirectUris: "[""https://oauth.pstmn.io/v1/callback""]” Type: “confidential”

Note: i am using EF Core + Identity Core + Openiddict

Issue Analytics

  • State:closed
  • Created 9 months ago
  • Comments:7 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
kevinchaletcommented, Dec 28, 2022

Actually yes… You’re right, I was trying to inject OpenIddictScopeManager through the constructor of one of my controllers. I removed it and now everything seems to be working fine. Thank you, Kevin!

Awesome!

Happy holidays! ❄️

1reaction
kevinchaletcommented, Dec 22, 2022
  1. how do i set an audience for a client? looking at the /userinfo it seems as if clientid == audience, is this correct?

Audiences of access tokens are controlled using principal.SetResources(...). To make that dynamic, you can use scopes and attach a list of resources via OpenIddictScopeDescriptor.Resources.

In practice, a client application is never the audience of an access token (the exception is when this application corresponds to a resource server doing OAuth 2.0 introspection: in this case, you have a client_id for the API and the resources must contain this client identifier).

Read more comments on GitHub >

github_iconTop Results From Across the Web

Setting up custom scope for OpenIddict MVC Core 2.0
I add the scope to my options when calling AddOpenIdConnect in my client so it will be added to requests. options.Scope.Add(MyClaimsConstants.
Read more >
Entity Framework Core integration - OpenIddict documentation
Basic configuration. To configure OpenIddict to use Entity Framework Core as the database for applications, authorizations, scopes and tokens, you'll need to:.
Read more >
DbContext Lifetime, Configuration, and Initialization
This article shows basic patterns for initialization and configuration of a DbContext instance. The DbContext lifetime. The lifetime of a ...
Read more >
API Scopes
The original OAuth 2.0 specification has the concept of scopes, which is just defined as the scope of access that the client requests....
Read more >
Data Scopes
In short, a data scope is a class which derives from the base class DataScope which defines which entities to fetch and persist,...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found