I apologize in advance for this because I have no knowledge of security in general and IdentityServer in particular.
I am trying to configure IdentityServer for security management for an Asp.Net MVC application.
I follow the manual on my website: Asp.Net MVC with IdentityServer
However, I am doing something a little different from the fact that I have a separate project for the "Server" part of Identity, which leads to 2 Startup.cs files, one for the application and one for Identity Server
For an application, the Startup.cs file is as follows:
public class Startup { public void Configuration(IAppBuilder app) { AntiForgeryConfig.UniqueClaimTypeIdentifier = Constants.ClaimTypes.Subject; JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>(); app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = "Cookies" }); app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions { Authority = "https://localhost:44301/identity", ClientId = "baseballStats", Scope = "openid profile roles baseballStatsApi", RedirectUri = "https://localhost:44300/", ResponseType = "id_token token", SignInAsAuthenticationType = "Cookies", UseTokenLifetime = false, Notifications = new OpenIdConnectAuthenticationNotifications { SecurityTokenValidated = async n => { var userInfoClient = new UserInfoClient( new Uri(n.Options.Authority + "/connect/userinfo"), n.ProtocolMessage.AccessToken); var userInfo = await userInfoClient.GetAsync();
For the authentication server, the startup.cs file
public class Startup { public void Configuration(IAppBuilder app) { app.Map("/identity", idsrvApp => { idsrvApp.UseIdentityServer(new IdentityServerOptions { SiteName = "Embedded IdentityServer", SigningCertificate = LoadCertificate(), Factory = InMemoryFactory.Create( users: Users.Get(), clients: Clients.Get(), scopes: Scopes.Get()) }); }); } X509Certificate2 LoadCertificate() { return new X509Certificate2( string.Format(@"{0}\bin\Configuration\idsrv3test.pfx", AppDomain.CurrentDomain.BaseDirectory), "idsrv3test"); } }
I also create authorization manager
public class AuthorizationManager : ResourceAuthorizationManager { public override Task<bool> CheckAccessAsync(ResourceAuthorizationContext context) { switch (context.Resource.First().Value) { case "Players": return CheckAuthorization(context); case "About": return CheckAuthorization(context); default: return Nok(); } } private Task<bool> CheckAuthorization(ResourceAuthorizationContext context) { switch(context.Action.First().Value) { case "Read": return Eval(context.Principal.HasClaim("role", "LevelOneSubscriber")); default: return Nok(); } } }
So, for example, if I define a controller method that is decorated with the ResourceAuthorize attribute, for example
public class HomeController : Controller { [ResourceAuthorize("Read", "About")] public ActionResult About() { return View((User as ClaimsPrincipal).Claims); } }
Then, when I first try to access this method, I will be redirected to the default login page.
However, I donβt understand why, when I log in with the user defined for the application (see below),
public class Users { public static List<InMemoryUser> Get() { return new List<InMemoryUser> { new InMemoryUser { Username = "bob", Password = "secret", Subject = "1", Claims = new[] { new Claim(Constants.ClaimTypes.GivenName, "Bob"), new Claim(Constants.ClaimTypes.FamilyName, "Smith"), new Claim(Constants.ClaimTypes.Role, "Geek"), new Claim(Constants.ClaimTypes.Role, "LevelOneSubscriber") } } }; } }
I get 403 error carrying error = "insufficient_scope".
Can someone explain what I'm doing wrong?
Any subsequent attempt to access the action method returns the same error. It seems to me that the user I identified has the correct claims to be able to access this method. However, claims checking only happens once when I first try to access this method. After logging in, I get a cookie, and application verification is not performed during subsequent attempts to access the method.
I'm a little lost and will be grateful for the help in clearing.
Thanks in advance.
EDIT: here are the slides and client classes
public static class Scopes { public static IEnumerable<Scope> Get() { var scopes = new List<Scope> { new Scope { Enabled = true, Name = "roles", Type = ScopeType.Identity, Claims = new List<ScopeClaim> { new ScopeClaim("role") } }, new Scope { Enabled = true, Name = "baseballStatsApi", Description = "Access to baseball stats API", Type = ScopeType.Resource, Claims = new List<ScopeClaim> { new ScopeClaim("role") } } }; scopes.AddRange(StandardScopes.All); return scopes; } }
And the class Client
public static class Clients { public static IEnumerable<Client> Get() { return new[] { new Client { Enabled = true, ClientName = "Baseball Stats Emporium", ClientId = "baseballStats", Flow = Flows.Implicit, RedirectUris = new List<string> { "https://localhost:44300/" } }, new Client { Enabled = true, ClientName = "Baseball Stats API Client", ClientId = "baseballStats_Api", ClientSecrets = new List<ClientSecret> { new ClientSecret("secret".Sha256()) }, Flow = Flows.ClientCredentials } }; } }
I also created a custom filter attribute, which I use to determine when requirements checking is done.
public class CustomFilterAttribute : ResourceAuthorizeAttribute { public CustomFilterAttribute(string action, params string[] resources) : base(action, resources) { } protected override bool CheckAccess(HttpContextBase httpContext, string action, params string[] resources) { return base.CheckAccess(httpContext, action, resources); } }
The breakpoint only hits the original request for the URL. In subsequent queries, the filter attribute breakpoint does not fall, and therefore no validation occurs. This is surprising to me, as I suggested that a check should be done every time a url is requested.