ASP.NET Core 2.0 makes it very easy and straightforward to setup a cookie authentication mechanism in your application. Framework provides numerous ways to achieve that, with or without ASP.NET Core Identity membership system.
This post is part of a series on ASP.NET Core 2.0 Authentication and I am about to talk about cookie authentication and claims transformation.
Application
In this demo, I am going to talk about claims transformation in ASP.NET Core 2.0. The application in question is based on previous post of this series, ASP.NET Core 2.0 Cookie Authentication - Local logins, so if you haven't followed along, it would be better to check this out first before diving into code that's coming next.
As I mentioned earlier, this application is based on a previous demo, which is using cookie authentication to authenticate a valid user into the MVC web application. I thought about making this a little bit more interesting, so I stripped out the in-memory user store, and I replaced it with a database back-end store, more specifically on SQL Server.
The schema implemented is the following:
So, we have a table where we store our users and a UserClaims
table, whereas we store additional claims for a given user.
Goal of this application is to create a cookie with some fixed/static claims and then enhance that cookie with additional claims that come from the back-end store. All claims for the authenticated user will be shown in the /profile
page.
Our web application remains an ASP.NET Core 2.0 MVC application.
For back-end store I have chosen Microsoft SQL Server 2014.
Application is communicating with database through Syrx, a fun, modular micro-ORM wrapper, which is based on Dapper. If you haven't yet tried it, please give it a shot, as it's goal is to make developer's life easier and works across all the same providers supported by Dapper including MySQL, PostgreSQL and SQL Server. For the rest of the series, I am going to use this library to communicate with relational database stores.
Disclaimer: I am contributor on Syrx. I plan to dedicate a few posts on Syrx, so stay tuned.
Code can be found onGithub.
What is claims transformation
An application's life cycle does not end when a team delivers it for production usage. Applications are maintained and they live as long as the organization building them gains profit (else they probably pull the plug and the lights go off - so might the team). So, throughout its life an application goes through maintenance (i.e. bug fixing) or enhancement, which essentially means, more features are added.
This growth sometimes means, that the application might require more than it already has. For example, the application that was build initially had some limited requirements and for authentication it needed only a cookie with some specific claims. That is okay, but what happens when new requirements come into play, which might mean that the authentication mechanism should be enhanced to meet those requirements. What about additional information about users, which might change frequently and needed for specific actions/behaviors in the application?
Or what about the scenario in which we have more claims for a user than we can put into his/hers authentication cookie?
Enter claims transformation
Claims transformation can be used to modify user's claims on incoming requests, thus storing minimal amount of information in the authentication cookie and enhancing the authentication process, by adding the rest as the request comes into the server.
These claims can be stored in a database and retrieved for each request, adding more value into user's identity.
ASP.NET Core 2.0 has the IClaimsTransformation
interface, which can be implemented by a service, for example, to add/remove/edit claims for a authenticated user. Use the TransformAsync
method to define your application logic.
Then, we just need to add this class into DI and the framework knows what to do from here.
That said, let's take a look in the implementation.
Implementation
First, I will talk briefly about database and how to provision your db with schema and data.
Schema for User
and UserClaims
tables is the following:
In order to not overwhelm this post with SQL related code, if you wish to follow along, go to my repository and under sql
folder run the setup.cmd
command in command prompt window. This contains some batch code, which is using sqlcmd
utility to setup the database schema and provision these tables with data. It can be found here.
Clone the repository in your local machine, open a command prompt window, navigate to the directory where setup.cmd
lives and type setup.cmd
. Great, your database now is ready, with schema and data in place.
Please note, this is how I will provision database schema and data for the rest of the series as well.
Let's proceed to the rest of the application.
Screen should be similar to the following.
Models
Now that we have the back-end store ready, let's create the related POCO models that we are going to use in the application. First, the User
model
And the UserClaims
model
As you see, nothing special, we just modeled our tables into valid C# classes.
Repositories
Having a database as a back-end store, we have to implement our own repositories to deal with it. Repositories provide a way to abstract away the underlying store, by hiding its implementation details. In this case, our repositories will hide away the implementation details of Syrx ICommander<>
from the service layer.
Let's see the UserRepository
implementation, which is responsible to fetch users by username.
Pretty neat huh? I inject the Syrx's ICommander
in the constructor and to query the database, I use the QueryAsync<User>(new { userName })
method, passing the SQL parameters for the query. That's pretty much it! You might be wondering, where is the SQL code? Well, Syrx does a very good job abstracting this away from C# code, as it comes with some configuration in hand.
In appsettings.json
, I have declared some configuration for this method, which contains the SQL code that is executed against the database.
Same applies for UserClaimsRepository.cs
, which returns a list of claims for a user
Now that repositories are ready, let's move into services.
Services
If you have been following along, you might remember from a previous post, I was using the UserService
service to validate user credentials against an in-memory store. Let's see how this service is changed, now that it has to use a repository to fetch data from.
Method ValidateUserCredentialsAsync
has not changed much, at least at its core, its behavior remains the same, it retrieves a user by username and validates against that user the given password (beware, passwords are passed as plain text here, they'd better be hashed).
FindUserByUserNameAsync
is just fetching a single user by his/hers username.
UserClaimsService
follows the same norm, it implements the method FindUserClaimsByUserNameAsync
and calls the underlying repository to get the desired data.
Great, now services are ready, we can move on to controllers!
Controllers
Only AuthController
has seen some changes from previous post, while HomeController
remained the same.
No major changes though, as only a few lines are added into the LoginAsync
method, as this demo sets up more claims for the authenticated user.
So in this demo, we create a new authentication cookie for a user with the following claims in place:
- NameIdentifier
- Name
- GivenName
- Surname
- DateOfBirth
If we could run the application now (assume that we have registered all required services and repositories in DI), we wouldn't be able to see any difference from the previous one, as from functionality perspective, it does the same. And it does not yet include the additional claims that are stored in the database.
We have some additional claims stored for the user, namely:
- Gender
- Country
- MobilePhone
This is the user's profile store. You can store whatever you like there. Most common is to store identity related data that is frequently changing, which is a problem that claims transformation solves.
Our profile store at the moment is the UserClaimsRepository
repository, which implementation hides behind UserClaimsService
. We need to find a way to add these additional claims to the user's identity.
Claims transformation service
Let's create a new class, I name it ProfileClaimsTransformationService
, it's a mouthful, I know, and implement the IClaimsTransformation
interface.
Let me add the implementation and I will walk you through.
First, I inject the profile store, aka the IUserClaimsService
, so I can have access to underlying data.
All heavy-lifting is done in TransformAsync
method, which receives a ClaimsPrincipal
. Be advised, that this method is called on every incoming request, of course when the user is authenticated, so you should keep it as lean as possible, as it might affect your application's performance if the code that runs there is doing too much.
In TransformAsync
, first, I get the authenticated identity. If it is null
, then I return immediately.
Then, I get the NameIdentifier
claim (I could as well fetch the Name claim, as it is more common to see a username in the Name
claim, though in my case I have set the NameIdentifier
as well as the Name
claim to have the username value. Most common use is to store the user's GUID
in NameIdentifier
and the username in Name
, though many applications store username in both claim types) and if I don't find it, then I cannot proceed with completing the user's profile, returning the principal untouched, on the spot.
After that, having the NameIdentifier
, I am using the IUserClaimsService
service to fetch all stored claims for that user and if I don't find anything, I just return the ClaimsPrincipal
untouched.
Now that I got the additional stored claims for that user, it's time to update his ClaimsPrincipal
, by creating a new ClaimsPrincipal
, which means that we need a new ClaimsIdentity
as well, leading us in creating a new claims list with all the claims the user already had and the ones that he got from the profile store.
Word of caution here. You might argue that it is faster to just modify the existing ClaimsPrincipal
object, by adding the claims that we got into the Claims
list. That seems perfectly reasonable, except that this is risky, because this method is called in each incoming request, which means it might be called multiple times, thus resulting in adding duplicate claims.
Claims transformation should adopt a more defensive approach and return a new principal when called.
Configuration
We are ready to go, but not yet! We need to register this bad boy on the DI, so framework will be able to pick it up and do its magic behind the scenes. We need to register the IClaimsTransformation
object, which should be the ProfileClaimsTransformationService
and all the other related repositories and services. Rest of the Startup.cs
is not changed since previous post.
I am not going to show all configuration setup for this application, for more info check the source code in the repository, I will show you the bare minimum, the services and the claims transformation service registration.
Line 5 is the line of interest here, this registers the IClaimTransformation
in DI, so our application now can perform claims transformation.
If you've been following along, run the application now and navigate to /profile
, you will be able to see the same screen as the one presented earlier (see at top) in this post.
Summary
In this post, we saw how to define a profile store for an authenticated user, how to enhance his identity through this store, using the IClaimsTransformation
interface by ASP.NET Core 2.0, as well as some caveats this approach has.
In next related posts, I am going to talk about how to respond to back-end changes, by utilizing some cookie events and how these are related with claims transformation, as well as talk about authorization policies.
Code repository can be found here.
This post is part of the ASP.NET Core 2.0 Authentication series.
- ASP.NET Core 2.0 Cookie Authentication - Local logins
- ASP.NET Core 2.0 Authentication with local logins - Implementing claims transformation
- ASP.NET Core 2.0 Authentication with local logins - Responding to backend changes
- ASP.NET Core 2.0 Authentication with local logins - Implementing custom authorization policies
- ASP.NET Core 2.1 Authentication with social logins
- ASP.NET Core 2.0 Authentication with social logins - Implementing a profile store
- ASP.NET Core 2.0 Authentication with Azure Active Directory
- ASP.NET Core 2.0 Authentication with Azure Active Directory B2C
- ASP.NET Core 2.0 Authentication, IdentityServer4 and Angular
Comments powered by Disqus.