📜 ⬆️ ⬇️

Windows Identity Foundation - for ASP.NET MVC projects


In this article, I would like to talk about how you can use the Windows Identity Foundation in your ASP.NET MVC projects, and write your Identity Server on a WIF platform. Firstly, because there is enough general information on the Internet, but when it comes to specifics, problems arise. Since the ideology and particular cases can still be found, but when it comes to specifics, you have to collect bit by bit. And secondly, what Microsoft is now offering, using add-ons on Visual Studio, is not quite good, I would even say, is completely inappropriate when developing solutions that are more complicated than a home page or a business card site. Among other things, I don’t really like it when the mythical configuration wizard did what it did with the solution, and said that “it should work”.

As an example, we will create, and configure, the most primitive client that will authenticate through the most primitive authorization server (Identity Server) working under the WS-Federation protocol.

In order to decide in which cases it makes sense to “make a fuss” with your authorization server, just look at how it works. So, we want to provide the client with several services, for example, several sites, for example with WCF services, and let's say REST API. Agree that it will be quite uncomfortable for the user to separately log in to each of our sites when switching from one to another. There is a rather simple idea, which is that when a user is authorized on one of the services (resources), a certain Token is issued. And later, another, or the same, service (resource) already trusts the authorized user, based on the client’s existence of this very token, and so on ...

Just for clarity of understanding, I will give the comparison you like (unfortunately I found references to it). For example, a person receives a passport, after a certain verification procedure, and later, when submitting his passport, a person is trusted, based on his passport. In this comparison, the person’s passport is the client's token.
')
We can immediately conclude that it is often meaningless to issue a “passport” if it will be checked only in one place.

Of course, the token, like the passport, has a lifetime, and in the same way, the token can be recreated, based on the obsolete one. And by analogy, like a passport, a token can be different, i.e. to be almost anything, either it is a request header or a base64 string, such as JWT Token ( JSON Web Token ). And in fact, the token itself contains information about itself (the time when it was created last time, the public key of the certificate, etc.), as well as a list of stamps containing information about the client. To describe the token, we will use the SAML ( Security Assertion Markup Language ) language .

Another important concept is the claims. Brands are part of our token, and carry information about the client as a whole. Actually, this is a dictionary consisting of a Key / Value pair, in which Key is a namespace describing the type of the Value field, and the Value field itself is a simple string. In .Net this is represented by a typed list:

var claimsList = new List<Claim> { new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", "Tester") }; 

There are a number of registered neymspeysov, which is more than enough to describe the client profile, from the list of roles to avatars. If desired, we can create our own namespaces, the only thing to remember is that the object itself Claim, must be serialized.

From here you can probably make two main conclusions, the first is that for simply authorizing a user on the site, using Identity Server is simply unjustified, and the second is that since we are going to transfer a token between different resources, we will need transport security in the first place. And so we will start, and perhaps we will begin with setting up the transport, because if we don’t have a trusted transport, we simply won’t be able to work anything, and for this, first of all, we will need a certificate.

Creating a certificate


Approximately from this point on, many developers have questions about creating a certificate, setting up HTTPS on the server, and so on. To work and debug, we need the IIS installed locally, a simple ASP.NET MVC application that will be our client site, and a trusted certificate. We need not just a certificate, but a certificate issued for any domain name, to buy it, for testing purposes is not economically profitable, so we will make it ourselves.

For example, the domain name that we will use for testing purposes will be identity.com . First, to create a certificate, we use the makecert utility.
 makecert -r -n "CN=*.identity.com" -cy authority -b 01/01/2000 -e 01/01/2099 -a sha1 -sr localMachine -sky Exchange -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 -sv identity.com.pvk identity.com.cer 

As a result of the execution, we will get two identity.com.pvk and identity.com.cer files. This is all very clear, and there is more than enough information on the makecert utility. The only point that I would like to dwell on in more detail is the CN for which we issue the certificate. If you issue a certificate just for identity.com , then, in the future, it will be inconvenient to locally model the situation with distributed resources, but using * greatly simplifies our task ie * .identity.com . Using * allows us to locally create an arbitrary number of domains like “any name” .identity.com .

Next, to verify the publisher's certificate, we use the cert2spc utility.

 cert2spc identity.com.cer identity.com.spc 

And as a result, we get the identity.com.spc file, which we need for the pvk2pfx utility. With the help of which we will generate the pfx file we need .
 pvk2pfx -pvk identity.com.pvk -spc identity.com.spc -pfx identity.com.pfx -pi "qwerty" 

As a result, we received an identity.com.pfx file containing a private key with the password qwerty. It remains to register it IIS and in the system.

HTTPS setup


In order for our IIS server to start working with our certificate, first, we need to import our pfx file into the Trusted Root Certification Authorities zone through the MMC snap-in, and perform the Import in the IIS server itself, in the Server Certificates section.

Now everything is ready for setting up our client site. To do this, create a new site in IIS, with the name client.identity (in other matters, no matter what), the main thing is that the App Pool of our site works under .Net 4.0 (this is if the site is compiled under .Net 4.0, 4.5). And we point the Physical path to the directory of our client site.

Next, set up our HTTPS, in the Binding section. After selecting https in the type field, we need to select our generated certificate, and only after that the Host name field becomes available for editing. If we generated the certificate with " * ", then we can indicate almost any name of our site, the main thing would be to end with identity.com , i.e. the name of our test domain. Further we can change our bindings in the Bindings section of our site. Only the last “stroke” remains, so change the hosts file along the path (c: \ Windows \ System32 \ drivers \ etc \) and add a line with the name of our site binding:
 127.0.0.1 client.identity.com 

Everything, you can check the work of the local https, just to the address: client.identity.com .

As a result, we should see our web application, and in the address bar, the fact that our certificate is trusted, i.e. no browser warnings.

If you download IdentityTrainingKitVS2010 from the Microsoft site, you can make your life a little easier by running SetupCertificates.cmd, for example, following the path IdentityTrainingKitVS2010 \ Labs \ MembershipAndFederation \ Source \ Setup \ Scripts \ SetupCertificates.cmd

This script will do almost the same thing, only for the already prepared certificate from the examples localhost.pfx (it has the password xyz). Accordingly, access to the site (for example, Default Web Site) will work through localhost, and all your applications that need to work via https should be created either as a Web Site, but as a Web Application, under a localhost site.

Configuring the client application


Now we need to make some customization of our client application. To begin with, in the project settings, set the address of our site for the Custom Web Server field.

This will give Visual Studio the opportunity, at startup, to automatically do Attach to Process to the w3wp.exe process (IIS site process).

Now, we need to deal with the references of our site, and add two assemblies from GAC, System.IdentityModel.dll and System.IdentityModel.Services.dll . As well as removing unnecessary, what will disturb us is NUget DotNetOpenAuth packages, we will not need them, but will only interfere, and for this, you need to remove the Microsoft.AspNet.WebPages.OAuth package. If, for some reason, you do not want to touch them, then as an option, is to set up registration in web.config.

And the last step in setting up the client application is setting up the web.config itself. First, in the sysytem.web section, set the authentication method to none.
 <authentication mode="None"/> 

Next, register our section for the Identity Model in the configSections section:
 <section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" /> <section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" /> 

Add and configure these sections, first system.identityModel:
 <system.identityModel> <identityConfiguration saveBootstrapContext="true"> <audienceUris> <add value="https://client.identity.com/" /> </audienceUris> <issuerNameRegistry type="System.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> <trustedIssuers> <add thumbprint="BDB8FF11361F312C06EDF1D20B05E775A123BE22" name="https://server.identity.com/issue/wsfed" /> </trustedIssuers> </issuerNameRegistry> <certificateValidation certificateValidationMode="None" /> </identityConfiguration> </system.identityModel> 

To begin with, in the audienceUris section, add the address of our client site, then add an entry in the trustedIssuers section, where the thumbprint is the Thumbprint value from our generated identity.crt file, or another transport certificate the server will work on.

Only without spaces. And the name field is the address of our future server, for example, in our case server.identity.com + / issue / wsfed. It is not necessary to use wsfed, especially in our case a future server - it could be anything. Just wsfed is short for WS-Federation .
Next, add the system.identityModel.services section:
 <system.identityModel.services> <federationConfiguration> <cookieHandler requireSsl="true" /> <wsFederation passiveRedirectEnabled="true" issuer="https://server.identity.com/issue/wsfed" realm="https://client.identity.com/" reply="https://client.identity.com/" requireHttps="true" /> </federationConfiguration> </system.identityModel.services> 

Everything is simple enough, iisuer is our server from the section above, and reply is where the address is, then reply to the server.

It remains to register the modules in the system.webServer section.
 <modules> <add name="WSFederationAuthenticationModule" type="System.IdentityModel.Services.WSFederationAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" /> <add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" /> </modules> 

Everything, it is possible to check the work of our client, it is enough to try with View to call any method or method of the controller marked with the Authorize attribute.

After starting the application (by F5), and the user tries to call our method, we will see a GET request to the address of our future server:
 https://server.identity.com/issue/wsfed?wa=wsignin1.0&wtrealm=https%3a%2f%2fclient.identity.com%2f&wctx=rm%3d0%26id%3dpassive%26ru%3d%252fAccount&wct=2014-09-23T13%3a36%3a21Z&wreply=https%3a%2f%2fclient.identity.com%2f 

This is an authorization request from the client to the server at server.identity.com/issue/wsfed .

The above is quite enough for minimal configuration of a client application working with WIF Identity Server, even if you use a third-party server, the main thing is for the server to support WS-Federation .

Server creation


Before you start creating an Identity server, you need to mention such a thing as STS ( Security Token Service ). In fact, this is a service, and it does not matter in which language and platform it is written. And our ASP.NET MVC Identity Server is essentially a UI shell. To create our own STS, since we still use .Net, it will be convenient for us to use the tools that are in the platform.

Our Identity Server, as well as the client, is essentially an ASP.NET MVC application, for which you also need to configure HTTPS, and, in our case, we will assign it a binding server.identity.com using the same one generated by us certificate.

Create a SignIn View for the SignIn method of the Account controller, and add an entry to the web.config:
 <authentication mode="Forms"> <forms loginUrl="~/Account/signin" timeout="2880" /> </authentication> 

And add to routes, a record for the controller method that will be executed when accessing via the issue / wsfed path.
 routes.MapRoute("wsfederation", "issue/wsfed", new { controller = "WSFederation", action = "issue" }); 

This is the path our client <> / issue / wsfed addresses. If we control the controller, mark it with the Authorize attribute, then the client application, when trying to log in, will first fall on the SignIn method of the Account controller, which in turn will return View, with the login form of our server.

Next, the user enters the data required to enter (for example, login password), and falls on the Issue method. Please note that RequestString does not change, but remains the same as the client application.

In order for our server to understand what exactly the client wants from it, let's sort the query arguments:
  public ActionResult Issue() { WSFederationMessage message = WSFederationMessage.CreateFromUri(HttpContext.Request.Url); // Sign in var signinMessage = message as SignInRequestMessage; if (signinMessage != null) { return ProcessWSFederationSignIn(signinMessage); } // Sign out var signoutMessage = message as SignOutRequestMessage; if (signoutMessage != null) { return ProcessWSFederationSignOut(signoutMessage); } return View("Error"); } 

Accordingly, the WSFederationMessage.CreateFromUri method returns the instance of the heirs of the abstract class WSFederationMessage. Next, perform actions, or login, or exit.

When logging in via the WS-Federation protocol, we run the static method:
 FederatedPassiveSecurityTokenServiceOperations.ProcessSignInRequest 

This method, based on the received instance of the SignInRequestMessage class, and the list of stamps (Claims), will form some RequestSecurityToken object, which is essentially our client token, and will give it to the GetScope method of our STS service. To create our STS service, we will inherit from the SecurityTokenService abstract class:
 public class TokenService : SecurityTokenService 

and block the GetScope method:
 protected override Scope GetScope(ClaimsPrincipal principal, RequestSecurityToken request) 

it is in this method that the requestSecurityToken.T. object will be analyzed or populated. directly the formation itself, and the verification of the client token. I simply see no point in describing the whole test, since the easiest way to go through the debugging method is because there is nothing trivial in the method.

In principle, the above described in its very minimum is enough to get a primitive Identity Server through which the client can log in.

If it will be interesting how this all works, I “glued together” a simplified client and server, on github .
This is just a lightweight version of the server https://github.com/thinktecture/Thinktecture.IdentityServer.v2 , collected so far, for the moment, for demonstration purposes and nothing more, and of course does not hold water.

Finally


What I would like, personally, I get as a result, so this is a full-fledged Identity Server, in which all work with the user profile is moved login, registration, social network, view your profile, etc. Accordingly, with the proper level of security. But in fact, that the server itself could be connected to the resource being developed, and each time not to spend time on the authorization system. And of course, I would like to integrate server operation, with WCF and REST services, with distribution of access to methods according to client roles. But this is only in the plans.

useful links


What is the Windows Identity Foundation: http://msdn.microsoft.com/ru-ru/library/ee748475.aspx
Sources of several Identity servers and useful libraries: https://github.com/thinktecture
A good report on Claims-based authorization: https://www.youtube.com/watch?v=WHSDIiwQlS8
More examples: http://claimsid.codeplex.com
And the codeproject: http://www.codeproject.com/Articles/504399/Understanding-Windows-Identity-Foundation-WIF

Source: https://habr.com/ru/post/238121/


All Articles