This article shows how authorization policies can be used together with IdentityServer4. The policies are configured on the resource server and the ASP.NET Core IdentityServer4 configures the user claims to match these. The resource server is also setup to encrypt a ‘Description’ field in the SQLite database, so it cannot be read by opening the SQLite database directly.
Code: https://github.com/damienbod/AspNet5IdentityServerAngularImplicitFlow
History:
2019-09-20: Updated ASP.NET Core 3.0
2018-06-22: Updated ASP.NET Core 2.1, ASP.NET Core Identity 2.1
Full history:
https://github.com/damienbod/AspNet5IdentityServerAngularImplicitFlow#history
Other posts in this series:
- Authorization Policies and Data Protection with IdentityServer4 in ASP.NET Core
- Angular OpenID Connect Implicit Flow with IdentityServer4
- Secure file download using IdentityServer4, Angular2 and ASP.NET Core
- Full Server logout with IdentityServer4 and OpenID Connect Implicit Flow
- IdentityServer4, WebAPI and Angular in a single ASP.NET Core project
- Extending Identity in IdentityServer4 to manage users in ASP.NET Core
- Implementing a silent token renew in Angular for the OpenID Connect Implicit flow
- OpenID Connect Session Management using an Angular application and IdentityServer4
Extending the Resource Server with Policies
An authorization policy can be added to the MVC application in the Startup class ConfigureServices method. The policy can be added globally using the filters or individually using attributes on a class or method. To add a global authorization policy, the AuthorizationPolicyBuilder helper method can be used to create the policy. The claimType parameter in the RequireClaim method must match a claim supplied in the token.
Then the policy can be added using the AuthorizeFilter in the AddMVC extension.
var guestPolicy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .RequireClaim("scope", "dataEventRecords") .Build(); services.AddMvc(options => { options.Filters.Add(new AuthorizeFilter(guestPolicy)); });
To create policies which can be used individually, the AddAuthorization extension method can be used. Again the claimType parameter in the RequireClaim method must match a claim supplied in the token.
services.AddAuthorization(options => { options.AddPolicy("dataEventRecordsAdmin", policyAdmin => { policyAdmin.RequireClaim("role", "dataEventRecords.admin"); }); options.AddPolicy("dataEventRecordsUser", policyUser => { policyUser.RequireClaim("role", "dataEventRecords.user"); }); });
To use and authenticate the token from IdentityServer4, the UseJwtBearerAuthentication extension method can be used in the Configure method in the Startup class of the MVC application.
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap = new Dictionary<string, string>(); app.UseJwtBearerAuthentication(options => { options.Authority = "https://localhost:44345"; options.Audience = "https://localhost:44345/resources"; options.AutomaticAuthenticate = true; options.AutomaticChallenge = true; });
The authorization policies can then be applied in the controller using authorization filters with the policy name as a parameter. Maybe it’s not a good idea to define each method with a different policy as this might be hard to maintain over time, maybe per controller might be more appropriate, all depends on your requirements. It pays off to define your security strategy before implementing it.
[Authorize] [Route("api/[controller]")] public class DataEventRecordsController : Controller { [Authorize("dataEventRecordsUser")] [HttpGet] public IEnumerable<DataEventRecord> Get() { return _dataEventRecordRepository.GetAll(); } [Authorize("dataEventRecordsAdmin")] [HttpGet("{id}")] public DataEventRecord Get(long id) { return _dataEventRecordRepository.Get(id); }
Adding the User Claims and Client Scopes in IdentityServer4
The resource server has been setup to check for claim types of ‘role’ with the value of ‘dataEventRecords.user’ or ‘dataEventRecords.admin’. The application is also setup to check for claims type ‘scope’ with the value of ‘dataEventRecords’. IdentityServer4 needs to be configured for this. The IdentityResource and the ApiResource is configured for the client in the IdentityServer AspNetCore project.
public static IEnumerable<IdentityResource> GetIdentityResources() { return new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Profile(), new IdentityResources.Email(), new IdentityResource("dataeventrecordsscope",new []{ "role", "admin", "user", "dataEventRecords", "dataEventRecords.admin" , "dataEventRecords.user" } ), new IdentityResource("securedfilesscope",new []{ "role", "admin", "user", "securedFiles", "securedFiles.admin", "securedFiles.user"} ) }; } public static IEnumerable<ApiResource> GetApiResources() { return new List<ApiResource> { new ApiResource("dataEventRecords") { ApiSecrets = { new Secret("dataEventRecordsSecret".Sha256()) }, Scopes = { new Scope { Name = "dataeventrecords", DisplayName = "Scope for the dataEventRecords ApiResource" } }, UserClaims = { "role", "admin", "user", "dataEventRecords", "dataEventRecords.admin", "dataEventRecords.user" } }, new ApiResource("securedFiles") { ApiSecrets = { new Secret("securedFilesSecret".Sha256()) }, Scopes = { new Scope { Name = "securedfiles", DisplayName = "Scope for the securedFiles ApiResource" } }, UserClaims = { "role", "admin", "user", "securedFiles", "securedFiles.admin", "securedFiles.user" } } }; }
Then users are also configured and the appropriate role and scope claims for each user. (And some others which aren’t required for the angular client.) Two users are configured, damienboduser and damienbodadmin. The damienboduser has not the ‘dataEventRecords.admin’ role claim. This means that the user cannot create or update a user, only see the list.
The security for the client application is configured to allow the different scopes which can be used. If the client requests a scope which isn’t configured here, the client application request will be rejected.
public static IEnumerable<Client> GetClients(IConfigurationSection stsConfig) { return new List<Client> { new Client { ClientName = "angularclient", ClientId = "angularclient", AccessTokenType = AccessTokenType.Reference, AccessTokenLifetime = 330,// 330 seconds, default 60 minutes IdentityTokenLifetime = 30, AllowedGrantTypes = GrantTypes.Implicit, AllowAccessTokensViaBrowser = true, RedirectUris = new List<string> { "https://localhost:44311", "https://localhost:44311/silent-renew.html" }, PostLogoutRedirectUris = new List<string> { "https://localhost:44311/unauthorized", "https://localhost:44311" }, AllowedCorsOrigins = new List<string> { "https://localhost:44311", "http://localhost:44311" }, AllowedScopes = new List<string> { "openid", "dataEventRecords", "dataeventrecordsscope", "securedFiles", "securedfilesscope", "role", "profile", "email" } } }; }
If the user damienboduser sends a HTTP GET request for the single item, the resource server returns a 403 with no body. If the AutomaticChallenge option in the UseJwtBearerAuthentication is false, this will be returned as a 401.
Using Data Protection to encrypt the SQLite data
It is really easy to encrypt your data using the Data Protection library from ASP.NET Core. To use it in an MVC application, just add it in the ConfigureServices method using the DataProtection extension methods. There are a few different ways to configure this and it is well documented here.
This example uses the file system with a self signed cert as data protection configuration. This will work for long lived encryption and is easy to restore. Thanks to Barry Dorrans @blowdart for all his help. A SQL database is used to store the data.
Note: For this to work the cert needs to be added to the cert store.
public void ConfigureServices(IServiceCollection services) { var connection = Configuration["Production:SqliteConnectionString"]; var cert = new X509Certificate2(Path.Combine(_env.ContentRootPath, "damienbodserver.pfx"), ""); services.AddDataProtection() .SetApplicationName("ResourceServer") .ProtectKeysWithCertificate(cert) .AddKeyManagementOptions(options => options.XmlRepository = new SqlXmlRepository( new DataProtectionDbContext( new DbContextOptionsBuilder<DataProtectionDbContext>() .UseSqlite(connection).Options ) ) );
Now the IDataProtectionProvider interface can be injected in your class using constructor injection. You can then create a protector using the CreateProtector method. Care should be taken on how you define the string in this method. See the documentation for the recommendations.
private readonly DataEventRecordContext _context; private readonly ILogger _logger; private IDataProtector _protector; public DataEventRecordRepository(IDataProtectionProvider provider, DataEventRecordContext context, ILoggerFactory loggerFactory) { _context = context; _logger = loggerFactory.CreateLogger("IDataEventRecordResporitory"); _protector = provider.CreateProtector("DataEventRecordRepository.v1"); }
Now the protector IDataProtectionProvider can be used to Protect and also Unprotect the data. In this example all descriptions which are saved to the SQLite database are encrypted using the default data protection settings.
private void protectDescription(DataEventRecord dataEventRecord) { var protectedData = _protector.Protect(dataEventRecord.Description); dataEventRecord.Description = protectedData; } private void unprotectDescription(DataEventRecord dataEventRecord) { var unprotectedData = _protector.Unprotect(dataEventRecord.Description); dataEventRecord.Description = unprotectedData; }
When the SQLite database is opened, you can see that all description fields are encrypted.
Links
Announcing IdentityServer for ASP.NET 5 and .NET Core
https://github.com/IdentityServer/IdentityServer4
https://github.com/IdentityServer/IdentityServer4.Samples
https://github.com/aspnet/DataProtection
ASP.NET Core 1 – Authorization using Policies
http://docs.asp.net/en/latest/security/data-protection/index.html
OAuth2 Implicit Flow with Angular and ASP.NET Core 1.0 IdentityServer4
The State of Security in ASP.NET 5 and MVC 6: OAuth 2.0, OpenID Connect and IdentityServer
[…] Authorization Policies and Data Protection with IdentityServer4 in ASP.NET Core – Damian Bowden […]
[…] Authorization Policies And Data Protection With Identityserver4 In Asp.net Core […]
[…] Authorization policies and data protection with Identity Server 4 in ASP.NET Core : Damien Bod 이 ASP.NET Core 에서 Identity Server 4 를 이용한 인증 정책과 데이터 보호에 대한 글을 작성하였습니다. […]
[…] Authorization policies and data protection with Identity Server 4 in ASP.NET Core : Damien Bod 이 ASP.NET Core 에서 Identity Server 4 를 이용한 인증 정책과 데이터 보호에 대한 글을 작성하였습니다. […]
Why is that in every sample or blog post I see, the Authority and Audience matches the Identity Server URLs? Isn’t the Audience the intended Resource server, or API server? Unless you are calling the UserInfo endpoint in the same Ids this Audience should be something like “api.domain.com”
Hi Zap
Thanks for your comment. The Audience value needs to match the aud value sent in the access_token. This value returned by IdentityServer is the default value created by IdentityServer when creating the access token. This could be changed, but it needs to match.
Greetings Damien
Hi Damien
I realize this tutorial is almost a year old – eternity for both ASP.NET Core and IdentityServer4. So if it’s obsolete – I apologize. But I also see a comment “2016.12.04: Updated to IdentityServer4 rc4” – so I am hopeful 🙂
You use app.UseJwtBearerAuthentication(options => { … } ) call. Is it still correct? I couldn’t find any mention of UseJwtBearerAuthentication in IdentityServer4 docs, and while the extension method is valid, it’s parameter is JwtBearerOptions, and not lambda, like in your example.
And while the fields in JwtBearerOptions are somewhat similar to your example – should I use this call *instead* of app.UseIdentityServerAuthentication() as all QuickStarts indicate, or in addition to?
Hopefully, the question makes sense.
Thanks
Hi Felix
Yes the arcticle is up to date. I’m updating now to rc5. Both Authentication extension methods work. You can use which ever one you prefer or see better.
Greetings Damien
Hi, Have you tried this with Custom Policies. I did but for some reason my requirement handler is not called when i visit the url protected by the policy
Nevermind I forgot to register services.AddSingleton();
cool
i have little issue with InMemoryUser section? where should i have to put? i check src code..but i cant see any InMemoryUser code..
Hi Brian, I use a database for the users, you could add it hereif you want
https://github.com/damienbod/AspNet5IdentityServerAngularImplicitFlow/blob/master/src/IdentityServerWithIdentitySQLite/Startup.cs#L65
Greetings Damien
thanks. a lot .
Hi Damien,
Your tutorials are fantastic, they’re the most comprehensive as i am trying to understand in detail using identityserver.
I have many questions regarding claims and scopes and i am hoping you could help.
I am not sure i understand them properly and how they fit in each piece of the puzzle.
You mentioned :
“the claimType parameter in the RequireClaim method must match a claim supplied in the token.” but i couldn’t find the claimType “scope” mentioned at [ RequireClaim(“scope”, “dataEventRecords”) ].
Also regarding the above statement, can you please explain why concept of claim type ends up being just another claim on the token ?
When defining an ApiResource, the resource has a UserClaims property, as well as you can define scopes for the resource and each scope can have claims, what is the difference between those ?
Are we defining here all those possible claims for this resource server ?
Thanks,
Hi Damien,
Your tutorials are fantastic, they’re the most comprehensive as i am trying to understand in detail using identityserver.
I have many questions regarding claims and scopes and i am hoping you could help.
I am not sure i understand them properly and how they fit in each piece of the puzzle.
You mentioned :
“the claimType parameter in the RequireClaim method must match a claim supplied in the token.” but i couldn’t find the claimType “scope” mentioned at [ RequireClaim(“scope”, “dataEventRecords”) ].
Also regarding the above statement, can you please explain why concept of claim type ends up being just another claim on the token ?
When defining an ApiResource, the resource has a UserClaims property, as well as you can define scopes for the resource and each scope can have claims, what is the difference between those ?
Are we defining here all those possible claims for this resource server ?
Thanks,
Hi sirajmansour
thanks. The claims scopes are defined in the Config.cs file.
https://github.com/damienbod/AspNet5IdentityServerAngularImplicitFlow/blob/master/src/IdentityServerWithIdentitySQLite/Config.cs
Scopes versus claims
https://stackoverflow.com/questions/41085286/different-between-api-scope-and-user-claim-in-identityserver4
https://leastprivilege.com/2016/12/01/new-in-identityserver4-resource-based-configuration/
https://github.com/IdentityServer/IdentityServer3/issues/67
This is not so clear.
Greetings Damien
Hey Damien, nice article. Instead of In Memory users, do you have any examples on loading the User Roles/Claims from an Identity DB into the token when the user logs in? I’m not sure where or how that happens. Thanks
Hi Damien
I followed your guide lines and the latest code but could not make authorization work in “ResourceWithIdentityServerWithClient” project with
[Authorize(AuthenticationSchemes = “Bearer”, Policy = “dataEventRecords”)] at class level
and [Authorize(“dataEventRecordsUser”)] or similar at method level.
It throws Status code 500 error.
If I remove authorization attributes or simple use [Authorize] it works
Any help in suggesting what is wrong in my approach?
Do you have any samples where the Users Roles/Claims are loaded from the Database? Using ASp.NET Identity with IdentityServer? I can get there “Core Framework” sample working for validating the user, but not sure how the Roles/Claims are loaded into the token from the database. Thanks
Is this code compact able with Asp.net core 2.1.
[…] Authorization Policies and Data Protection with IdentityServer4 in ASP.NET Core […]
Irrelevant, but any idea how one can do the :
[Authorize(“dataEventRecordsUser”)]
…in .NET Framework (the old framework)?
I cannot even find the .NET Framework version of the code of I.S., so that I take a look at it.
This is really frustrating… 😦
Replying to my previous comment:
OK, an example for .NET Framework is here, and it uses the normal Authorize attribute:
https://github.com/IdentityServer/CrossVersionIntegrationTests/tree/master/src/KatanaApiIdSrv4withRSA
But it is not clear how to enforce scope permissions to particular endpoints and I see no way to enforce policies either.
But I guess you put your scope to be enforced as a role like that:
[Authorize(“scope_to_enforce”)]
Hopefully…
I will see how that goes…