Identity Server integration with ASP MVC

iamprovidence
14 min readApr 27, 2023

--

So far, we have discussed how to implement cookie based authentication. Then you have learned about authorization, roles and permissions. Now it is time to enhance our knowledge and get familiar with an authentication beast in C# also known as Identity Server. I hope you are excited 😱

In case you just randomly appeared on this page, and have no clue what is going on, here is a small navigation list for you 😉

Authentication theory:

How to implement authentication in C# environment:

You may also be interested in those articles:

  • What is authentication schema in ASP about?
  • Authorization policy under the hood
  • Encapsulate authentication with DelegatingHandler
  • How to parse token on client side

Without any further delay, let’s get started.

A little about our domain…

Just like before, we are still a happy developer of an online store. Everything is going on great. We have simple ASP MVC app, implement small features time to time, fix bugs, and so on and so forth. Boring developer life, so to say. Our customer is satisfied. The business is growing. Stuff could not be better 😌

From an aerial view, our architecture is quite simple. Users use their browsers to communicate with our simple MVC app to do orders:

One day the customer appear with this great idea to create another application for users. Of course, it also requires authentication and authorization.

So you are rolling up your sleeves, breathing deeply and getting ready for work 😤 But, there is always a big BUT 🍑.

But you need to make, so a user authenticated once in one application can use another without filling in a login form again 🤔.

Of course, we can copy and paste endpoints, html pages, a lit of bit here, a lit of bit there… Although, if you have followed me for a while you should know, we don’t do that here.

Therefore, this is a time to introduce adult toys. Behold, a big daddy of authentication, Identity Server itself.

With it, our architecture will rise up to something like this:

Now, we have two different applications. They both are written with the help of ASP MVC. But, none of those know anything about authentication process. They just redirect you to Identity Server where you fill in a login form. After successful authentication, it redirects you back to MVC app. You can even open a second website, and it magically works without any additional authentication required.

Fascinating, is it? At least, that the plan 😆

Identity Server

So, what the hell IdentityServer is? 🤔

According to their own documentation:

IdentityServer4 is an OpenID Connect and OAuth 2.0 framework for ASP.NET Core.

Does it say anything to you? For me neither.

Here are things you need to know about IdentityServer:

  • It is a framework like ASP, EntityFramework or any other framework. Meaning it is extremely agile and configurable, thus lots of stuff we get out of the box
  • It has something to do with OpenID Connect and OAuth. What exactly does not interest us for now. Just know that it can do both, authentication and authorization
  • You can also think about it as a microservice, responsible for storing users, their permissions, authenticate them in and out of the system

Wait a second? So you are telling me I am going to use it? Why the hell I have read previous articles 😐

Don’t worry. This knowledge is still valid. It would be unfair from my side, to waste your time for nothing 😏. All those stuff you have learned so fat about cookies and permission, still can be applied. You will see it later.

Configure Identity Server

So, when you are ready, create a new empty ASP project, and call it “IdentityServer”. Delete all Controllers if any. We will do everything from scratch.

The first thing you need to do is to install next package:

Then open your Program.cs file. It should not have much configuration, only default ones. Enhance it with next services:

You can see we are registering IdentityServer, SingingCredential, Users, Clients and IdentityResources. Those are some of the main concepts that Identity Server operate.

We will populate them, one by one, and learn about those as stuff goes.

Let’s start by creating an empty class IdentityServerConfiguration:

That is where all Identity Server configuration will be living, duh 😒

TestUsers

The simplest concept here is users.

Alright, I think everybody knows and understand who users are. So there is no need to explain it once again. But I still will do it, just to complete the picture. 😃

Users are people who log into our system and using it.

You probably used to have them in database, but for demonstration purpose, let’s keep them in memory.

While Username and Password are obvious fields. Claims were discussed previously. The only unknown field remained is SubjectId. And that is simple. SubjectId is just an Id of the user.

So, why not calling it just an Id?

SubjectId, sub are common names for such things in different specification. So not to introduce another concept, developers of IdentityServer, have to live with it. And hence, we too.

Worse noticing, that SubjectId is a type of string, and not an integer. Meaning it can be anything string, number, GUID, serialized object and so. Poor man’s generics, so to say 😆. Also, the kind of obvious stuff with SubjectId, that it should be unique.

Resources

Resources, it is something we want to protect. Not a very descriptive, isn’t it? 🤔

Think about it this way. Our user has some personal data like email, first name, date of birth and so on. We would not like everybody to have access to it. However, for our MVC App1 to work properly, we need to know those stuffs. (I don’t know, maybe to show a user’s email somewhere in the headers, on top right corner for example. Who know what those damn developers came up with 🤷‍♂️). So when user authenticate to our system using MVC App1, it has to tell Identity Server to what resources it should have access. Different application, like MVC App2, may not need those data, who knows. The point is, Resources is something IdentityServer will put in cookies when user authenticate.

Now, let’s just list all available resources.

We can define our own resource, but that just redundant for our example. Let’s just add IdentityResources, meaning we want to protect user’s identity, stuff like Id, first name, email and so on.

Those are standard resources.

IdentityResources.OpenId — protect subject id

IdentityResources.Profile — protect user’s information (first name, last name…)

Don’t worry if you don’t understand it. You may do additional researches on it, but it is just stuff that you unconsciously copy/paste from project to project.

Clients

A Client is a software that authenticate via IdentityServer.

In our case, such software is our MVC applications.

Clients must be first registered with IdentityServer before they can use it. Those can be dynamic, but most of the time it is enough even in enterprise applications to have them in memory.

Here is an example how to define MVC client (Notice, to save some space, I have defined a single client for both apps, while in reality you may want to have separate clients with different configurations):

There are quite a lot, but those parameters are simple:

ClientId — just a name for your client you like. Can be anything, just write something you will understand in a week 😁

ClientSecrets — it has something to do with cryptology and other boring stuff 😂 In short, it is any string that is known by your client and Identity Server. Ideally, it should be something complicated and hard to guess, like ep0g8DqaAj9dj5cSniR3Xw1RfyoCs6S1. Also, do not put it right into the code, load it from configs, or db for God’s sake…

RedirectUris and PostLogoutRedirectUris — where to redirect after successful login and logout. Those urls are predefines and has next format {client-url}/signin-oidc and {client-url}/signout-callback-oidc accordingly. oidc in those URLs stands for OpenID Connect (don’t ask me. I have no idea what it is. Your friends have no idea what it is. Nobody has an idea what it is. If you accidentally end up in a conversation about authentication just bring OpenID Connect time to time, so people know you are an expert, that is enough 😁).

AllowedScopes — sets to which resources our client has access. Should match resource’s name. In our case, we have only defined two resources (OpenId, Profile) and we would like to access both of them in our MVC apps.

And the last one, the most confusing part is AllowedGrantTypes also known as type of authorization permission. It depends on what authorization methods are allowed. Here, we only have one method defined — authorization_code, which equals to Code grant type.

I guess it does not say you much 😅 Alright, let’s try one more time. All you need to understand at this point that there are different applications. Some are simple like ours MVC app. Some are more complex, written mostly with JS, Angular, React and so on. Others are for desktop or mobile. Applications are different, alright? Those applications can and will authenticate users differently too, depending on available options and security level those propose.

There are multiple authentication algorightm/methods/flows implicit, hybrid, authorization_code and so on. Your app can support many of them, it can support one, I mean it should support at least one. For our MVC apps, we will just use authorization_code.

Let’s see how it works:

  1. Firstly, we need a user. We also need an MVC App with protected endpoint (action marked with [Authorize] attribute). The user want to access this protected endpoint.
  2. When that happens, it gets redirected to Identity Server.
  3. Identity Server figures out what client it is, and what authentication method it uses. It sees that for MVC client we have authorization_code authentication method, which require interaction with user. Therefore, a login form is displayed.
  4. If the user enters the correct credential, it receives an authorization code.
  5. That code can be exchanged for authentication cookies.
  6. Finally, when that happens, Identity Server sends our cookies which allow users to access protected endpoint and freely use our app.

Why it is so complicated? Why do I need that code? Can we receive cookies at step 4?🤔

Yes, we can. Such authentication method do exists. However, this one is more secure 🔒. Don’t ask me why 🙃

Remember, this is just a simplified version of what happening, and even it seems to be confusing. In reality, it even more complicated.

Recap

Let’s do a quick recap to make sure you are still in a loop.

So far, we have done the following things:

  • Created a new Identity Server project.
  • Added users who have access to our system.
  • Listed all resources that can be accessed by our MVC apps.
  • Listed MVC apps, also known as clients.
  • Specified for Clients which user data they can access and what authentication method they support.

I hope you are still with me😅

Missing configurations

Alright, if you have followed me, you should have something similar in your configurations:

There are two things we have not discussed yet — AddIndentityServer() and AddDeveloperSigningCredentials().

AddIndentityiServer() is just a starting point for configuring Identity Server for our needs. It adds all basic services, validators, default endpoints and so on. Nothing really special:

On the other hand, AddDeveloperSigningCredentials() is somehow important. As name implies, it should be using only in developer environment, while for production there is AddSigningCredential(). In reality, you should provide with proper certificate, to make it more secure and bla bla bla…

I mean, honestly, who cares about that security stuff? Let’s just move on.

So, as I mentioned before, AddIdentityServer(), adds default endpoints for login and logout. But before implementing them, let’s make sure our Identity Server support controllers and view:

Of course, don’t forget about middlewares:

That should be it. Here you have a full configuration file:

Log in

Now when Identity Server configured we can add a login page.

The endpoint for it should have next signature:

{IdentityServerUrl}/account/login?returnUrl=XXXX

returnUrl is essential here. Without it, we would not know where to redirect our user after successful login.

So let’s implement that endpoint. It does not do much, just displays login page:

Next step would be to define an HTML page with some inputs for name and password:

Finally, we need a POST-endpoint where that page will be submitted:

The logic here is pretty straightforward:

We validate whenever user entered correct name and password. If those are wrong, we simply redirect him back to the login page to try once again. In case those match, we can authenticate him and redirect back to our MVC site.

So far, so good. Alright, now it is time to have a look at our Authenticate() method:

We just create new IdentityServerUser and authenticate it. Identity Server will create claims, identity and principal by itself, so we don’t have to worry about anything.

Wait, this looks familiar 🤔

Indeed 😃 Just compare it to what we have previously. There are quite a lot of similarity.

Configure MVC client

All the work for Identity Server is done. Now we just need to update our MVC app.

First thing we need to do in our MVC app is to change our endpoint and make it more secure. This can be done by adding [Authorize] attribute.

Everything you have learned so far about claims, roles, permission is still applicable. As I have promised 😏

Next step would be to configure authentication process in Startup.cs:

We are adding authentication, however this time two different schemas are used. This may look confusing to you, but it actually makes perfect sense 😃.

DefaultChallengeScheme — defines the configuration we would use to authenticate users. In this case, OpenIdConnect. Meaning, users will be redirected to Identity Server, where they can enter login and password and redirected back with all needed cookies.

DefaultScheme — defines scheme responsible for validation of those cookies. We could also use Identity Server for that. But remember, cookies are added to each request. Redirecting user on each request to Identity Server, just to validate whenever cookies are expired or not… Jesus, that just too much for it 😨 We can validate that by our MVC app itself!

Now, when those two schemas are defined, we need to configure them.

There is nothing special for cookies validation, so let’s just keep it as is, with default configuration.

While for authentications, there are few configurations we need to set:

  • Authority — is just an URL of our Identity Server, so MVC app know where to redirect our user
  • ClientId, ClientSecret — those defines are client and should match to what we have in IdentityServerConfiguration.GetClients()
  • ResponseType — what information we are expecting from Identity Server to receive after user has entered his data. As we established before, we are using authorization_code, so we expect to receive code.
  • ResponseMode — how exactly this information should be received. By default, it is done via POST request, but let’s just change it to GET, so we can see the code in the query.
  • SaveTokens — specifies that we want to store cookies in user’s browser. This is especially needed, when we want to use different MVC app to avoid authorization process when switching between different apps, that use the same IdentityServer. Which is exactly the case 😃

That is full configuration you need:

And of course, we need to register middlewares, so those configurations are actually used:

That’s it😃

Now when you try to access a protected endpoint you will be redirected to Identity Server, where you have to enter user’s credential and will be redirected back to our MVC app.

The only noticeable difference here is the amount of cookies we have:

😨 Yep, quite a lot of cookies to send for each request. But nahh, c’mon our app can handle it just fine 😌

Log Out

Now, when we know how to log in, we should implement a log-out.

With a central authentication service like Identity Server, it is not enough to clear the local application cookies. In addition, we also need to clear the central single sign-on session.

In your MVC app, you have to add such endpoint:

This will do next things:

  • SignOutAsync with “Cookies” will simply clear local cookies
  • SignOutAsync with “OpenIdConnect” will redirect us to log-out endpoint in Identity Server, which in our case has next route {IdentityServerUrl}/account/logout?logoutId=XXXX

So, let’s add a log-out endpoint with such signature to Identity Server:

We are free to implement it in the way we want. Just make sure to call SignOutAsync() to delete Identity Server cookies.

You can also see, that by using logoutId we can retrieve additional information, such as redirect URL, which is helpful, since we would like to redirect the user back to MVC app.

Missing claims

If you were following this article and trying everything in practice, you may notice that user’s claims defined in IdentityServerConfig are actually missing in our MVC app.

I know, I said that Identity Server, will handle everything for us, and actually I have lied to you 😅 It will handle everything, but you have to instruct it how to do it.

So, let’s fix it. In our MVC app, a few more configurations required when configuring authentication:

GetClaimsFromUserInfoEndpoint indicate that we actually DO want, having claims in our MVC app.

Then we have to specify which claims from Identity Server we want to map. We can set which claim to ignore and which we want to map manually, but we don’t need to make stuff complicated. Let’s just map all claims as they are with ClaimActions.MapAll().

The last step, we have to say “Hey, MVC threat this claim as name and this one as role”

So we can call it like this:

Instead of using this:

Just for simplicity, and so developers don’t get confusing by having null value even when name claim is present. 😄

Back to Identity Server. By default, it will not return all claims. We can override this behavior and extend that logic by implementing IProfileService.

Don’t forget to substitute new service in configurations.

The profile service is called whenever IdentityServer needs to return claims about a user to a client application.

Conclusion

Phew, what a journey 😮‍💨

I hope, you have managed to do so far. If so, feel proud of yourself. Not much have reached this point 😌

I highly encourage you, for better understanding, to try those code yourself. Compare to what we had previously with cookie authentication. Spot all similarities and difference. How login endpoint look, where it is placed, how it works. 😵

It may seem like a lot of code, while in reality it just a few files. Make sure you understand each line. I have deliberately avoided usage of in-build constants and preferred static string, so you can see where those values match. You can also try to change them and see the result. 🔄

What you have seen just describes the basics of Identity Server. I am planing to continue investigating it. If you are interested, and want to join me in discovering authentication process, don’t forget to follow me, to not miss anything ✅

Clap for this article, if you liked it 👏

You can also support me☕️

Ask a question in the comment section 💬

And keep coding🙃

--

--

iamprovidence

👨🏼‍💻 Full Stack Dev writing about software architecture, patterns and other programming stuff https://www.buymeacoffee.com/iamprovidence