Configure IdentityServer with Asp.Net MVC

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(); // create new identity and set name and role claim type var nid = new ClaimsIdentity( n.AuthenticationTicket.Identity.AuthenticationType, Constants.ClaimTypes.GivenName, Constants.ClaimTypes.Role); userInfo.Claims.ToList().ForEach(c => nid.AddClaim(new Claim(c.Item1, c.Item2))); // keep the id_token for logout nid.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken)); // add access token for sample API nid.AddClaim(new Claim("access_token", n.ProtocolMessage.AccessToken)); // keep track of access token expiration nid.AddClaim(new Claim("expires_at", DateTimeOffset.Now.AddSeconds(int.Parse(n.ProtocolMessage.ExpiresIn)).ToString())); // add some other app specific claim nid.AddClaim(new Claim("app_specific", "some data")); n.AuthenticationTicket = new AuthenticationTicket( nid, n.AuthenticationTicket.Properties); } } }); app.UseResourceAuthorization(new AuthorizationManager()); app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions { Authority = "https://localhost:44301/identity", RequiredScopes = new[] { "baseballStatsApi"} }); var config = new HttpConfiguration(); config.MapHttpAttributeRoutes(); app.UseWebApi(config); } } 

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.

+7
c # asp.net-mvc-4 identityserver3
source share
1 answer

You need to request the scope required for the api when the user logs in. Scope = "openid profile roles baseballStatsApi"

  Authority = "https://localhost:44301/identity", ClientId = "baseballStats", Scope = "openid profile roles baseballStatsApi", ResponseType = "id_token token", RedirectUri = "https://localhost:44300/", SignInAsAuthenticationType = "Cookies", UseTokenLifetime = false, 
+3
source share

All Articles