Claims-based authentication in MVC4 with .NET4.5 C# part 2: storing authentication data in an authentication session

In the previous post we built a simple claims-aware MVC4 internet application. We saw that calling the Authenticate method in CustomClaimsTransformer.cs with every page refresh might not be desirable. In this post we’ll look at caching possibilities so that we don’t need to look up the claims of the user in the DB every time they request a page in our website.

Basics

The claims transformation pipeline will look as follows with auth sessions:

Upon the first page request:

  1. Authentication
  2. Claims transformation
  3. Cache the ClaimsPrincipal
  4. Produce the requested page

Upon subsequent page requests:

  1. Authentication
  2. Load cached ClaimsPrincipal
  3. Produce the requested page

You can immediately see the benefit: we skip the claims transformation step after the first page request so we save the potentially expensive DB lookups.

By default the authentication session is saved in a cookie. However, this is customisable and there are some advanced scenarios you can do with the auth session.

The authentication session is represented by an object called SessionSecurityToken. It is a wrapper around a ClaimsPrincipal object and can be read and written to using a SessionSecurityTokenHandler.

Demo

Open the MVC4 project we started building in the previous post. In order to introduce auth session caching we need to start with our web.config, so open that file.

We need to define some config sections for System.identityModel and system.identityModel.services. Add the following sections within the configSection element in web.config:

<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" />

Build the application so that even IntelliSense will be aware of the new config sections when you modify web.config later on.

By default the authentication session feature will only work through SSL. This is well and good but may be an overkill for a local demo app. To disable it let’s add the following bit of XML somewhere within the configuration element in web.config:

<system.identityModel.services>
    <federationConfiguration>
      <cookieHandler requireSsl="false" />
    </federationConfiguration>
  </system.identityModel.services>

Remember to turn it on again for the production environment and install your X509 certificate.

The next step in the web.config is to register the module that will handle the auth sessions. Add the following module within the system.webServer element:

<modules>
      <add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"></add>
    </modules>

This module will be activated at the post-authentication stage. It will attempt to acquire the auth cookie and turn it to a ClaimsPrincipal.

The last step in web.config is to register our custom claims transformation class:

<system.identityModel>
    <identityConfiguration>
      <claimsAuthenticationManager type="ClaimsInMvc4.CustomClaimsTransformer,ClaimsInMvc4"/>
    </identityConfiguration>
  </system.identityModel>

The type value is built up as follows: [namespace.class],[assembly]. You can find the assembly name under the project properties.

We can now register the session in our code. Go to CustomClaimsTransformer.cs and update the Authenticate method as follows:

public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
        {
            if (!incomingPrincipal.Identity.IsAuthenticated)
            {
                return base.Authenticate(resourceName, incomingPrincipal);
            }

            ClaimsPrincipal transformedPrincipal = DressUpPrincipal(incomingPrincipal.Identity.Name);

            CreateSession(transformedPrincipal);

            return transformedPrincipal;
        }

…where CreateSession look as follows:

private void CreateSession(ClaimsPrincipal transformedPrincipal)
        {
            SessionSecurityToken sessionSecurityToken = new SessionSecurityToken(transformedPrincipal, TimeSpan.FromHours(8));
            FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionSecurityToken);
        }

We create a SessionSecurityToken object and pass in the transformed principal and an expiration. By default the auth session mechanism works with absolute expiration, we’ll see later how to implement sliding expiration. Then we write that token to a cookie. From this point on we don’t need to run the transformation logic any longer.

Go to Global.asax and comment out the Application_PostAuthenticateRequest() method. We don’t want to run the auth logic upon every page request, so this is redundant code.

Instead we need to change our Login page. Go to Controllers/AccountController.cs and locate the following HTTP POST action:

public ActionResult Login(LoginModel model, string returnUrl)

In there you’ll see the following code bit:

if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
            {
                return RedirectToLocal(returnUrl);
            }

It is here we’ll call our auth session manager to set the auth cookie as follows:

if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
            {
                List<Claim> initialClaims = new List<Claim>();
                initialClaims.Add(new Claim(ClaimTypes.Name, model.UserName));
                ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(initialClaims, "Forms"));
                ClaimsAuthenticationManager authManager = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.ClaimsAuthenticationManager;
                authManager.Authenticate(string.Empty, claimsPrincipal);
                return RedirectToLocal(returnUrl);
            }

We first create an initial set of claims. We only add in the Name claim as it is sufficient for our demo purposes but you’ll need to pass in everything that’s needed by the claims transformation logic in the custom Authenticate method. Then we create a new ClaimsPrincipal object and pass it into our transformation logic which in turn will fetch all the necessary claims about the user and set up the auth session. Note that we new up a ClaimsAuthenticationManager by a long chain of calls, but what that does is that it extracts the registered auth manager from the web.config, which is CustomClaimsTransformer.cs. We finally call the Authenticate method on the auth manager.

This code is slightly more complicated than in pure Forms based auth scenarios where a single call to FormsAuthentication.SetAuthCookie would have sufficed. However, you get a lot more flexibility with claims; you can pass in a whole range of input claims, can call your claims transformation logic and you also get a cookie that serialises the entire claim set.

Set a breakpoint at the first row of CustomClaimsTransformer.Authenticate. Run the application now. There should be no visible difference in the behaviour of the web app, but the underlying authentication plumbing has been changed. One difference in code execution is that the Authenticate method is not called with every page request.

Now log on to the site. Code execution should stop at the break point. Insect the incoming ClaimsPrincipal and you’ll see that the name claim we assigned in the Login action above is readily available:

Name claim available after auth session

Step through the code in CustomClaimsTransformer.cs and you’ll see that the SessionSecurityToken has been established and written to a cookie.

Where is that cookie?

With the web app running in IE press F12 to open the developer tools. Click the Network tab and press Start capturing:

Start capturing developer tools

Click on the About link on the web page. You’ll see that the URL list of the Developer Tool fills up with some items that the page loaded, e.g. site.css. You’ll see a button with the caption ‘Go to detailed view’ to the right of ‘Stop capturing’. This will open a new section with some new tabs. Select the ‘Cookies’ tab. You may see something like this:

Authentication session cookie

I highlighted ‘FedAuth’ which is the default name given the authentication session cookie. Its value is quite big as we’re serialising a whole claim set. Don’t worry much about the size of this long string. If the serialised value becomes too big, then the part that does not fit into FedAuth will be added to another cookie called FedAuth1 and so on. Cookies are partitioned into chunks of 2KB.

You may be wondering what the .ASPXAUTH cookie is doing there, which is the traditional auth cookie set in a forms-based authentication scenario. If you check the code in the Login action again you’ll see a call to WebSecurity.Login. It is here that the .ASPXAUTH cookie will be set. You can read more about WebSecurity in my previous blog posts on Forms auth here and here.

Logging out

This should be easy, right? Just press the Logout link and you’re done. Well, try it! You’ll see that you cannot log out; it will still say Hello, [username]. Recall that the auth session was persisted to a cookie and we specified an absolute expiration of 8 hours. Go to AccountController.cs and locate the Logoff action. You’ll see a call to WebSecurity.Logout() there which only removes the .ASPXAUTH cookie. Check the cookies collection in the Developer Tools:

web security removes aspxauth cookie

However, we still have our FedAuth cookie. So even if you log out, the FedAuth cookie will be sent along every subsequent request and from the point of view of the application you are still authenticated and logged in. Add the following code the the LogOff action in order to remove the FedAuth cookie as well:

FederatedAuthentication.SessionAuthenticationModule.SignOut();

Try again to log on and off, you should succeed.

Events

You can attach events to SessionAuthenticationModule:

  • SessionSecurityTokenReceived: modify the token as you wish, or even cancel it
  • SessionSecurityTokenCreated: modify the session details
  • SignedIn/SignedOut: raise event when the user signs in or out
  • SignOutError: raise event if there is an error when signing out

You can hook up the events in CustomClaimsTransformer.CreateSession as follows:

private void CreateSession(ClaimsPrincipal transformedPrincipal)
        {
            SessionSecurityToken sessionSecurityToken = new SessionSecurityToken(transformedPrincipal, TimeSpan.FromHours(8));            
            FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionSecurityToken);
            FederatedAuthentication.SessionAuthenticationModule.SessionSecurityTokenCreated += SessionAuthenticationModule_SessionSecurityTokenCreated;
        }

        void SessionAuthenticationModule_SessionSecurityTokenCreated(object sender, SessionSecurityTokenCreatedEventArgs e)
        {
            throw new NotImplementedException();
        }

Sliding expiration

SessionSecurityTokenReceived event is useful if you want to set a sliding expiration to the auth session. It’s generally a bad idea to set a sliding expiration to a cookie; a cookie can be stolen and with sliding expiration in place it can be used forever if the expiry date is renewed over and over again. So normally the default setting of absolute expiration is what you should implement.

You can reissue the cookie in the SessionSecurityTokenReceived event as follows:

private void CreateSession(ClaimsPrincipal transformedPrincipal)
        {
            SessionSecurityToken sessionSecurityToken = new SessionSecurityToken(transformedPrincipal, TimeSpan.FromHours(8));            
            FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionSecurityToken);
            FederatedAuthentication.SessionAuthenticationModule.SessionSecurityTokenReceived += SessionAuthenticationModule_SessionSecurityTokenReceived;
        }

        void SessionAuthenticationModule_SessionSecurityTokenReceived(object sender, SessionSecurityTokenReceivedEventArgs e)
        {
            SessionAuthenticationModule sam = sender as SessionAuthenticationModule;
            e.SessionToken = sam.CreateSessionSecurityToken(...);
            e.ReissueCookie = true;
        }

You will have access to the current session token in the incoming SessionSecurityTokenReceivedEventArgs parameter. Here you have the chance to inspect the token as you wish. Here we set the SessionToken property to a new token – I omitted the constructor parameters for brevity. You can obviously use the same session creating logic as in the CreateSession method, the CreateSessionSecurityToken is just another way to achieve the same goal.

Set a break point at…

SessionAuthenticationModule sam = sender as SessionAuthenticationModule;

…and run the application. Code execution will stop at the break point when logging in. The event will be then raised on every subsequent page request and the session security token will be reissued over and over again.

Cookie handling

It is a good idea to protect the auth session cookie so that it cannot easily be read. Fortunately the session token is by default protected by the token handler using DPAPI. The key that’s used to encrypt the token is local to the server. This means that you will run into problems in a web farm scenario where each server has a different machine key. In this case you’ll need a shared key.

You can build a new class that derives from SessionSecurityTokenHandler and set your encryption logic as you wish.

Alternatively starting with .NET4.5 there’s a built-in token handler that uses the ASP.NET machine key to protect the cookie. You can find more details on the MachineKeySessionSecurityTokenHandler class on MSDN. The shared key material will be used to protect the cookie. It won’t make any difference which server the browser connects to.

Server side caching

In case you need a large amount of claims to get the job done the auth session cookie size may grow considerably large. This may become an issue in mobile networking where the mobile device can have a slow connection. The good news is that session tokens can be cached on the server as well. It is only the session token identifier that will be sent back and forth between the browser and the server. The auth framework will find the claims collection on the server based on the identifier; you don’t need to write code to find it yourself. You can achieve this in code as follows:

SessionSecurityToken sessionSecurityToken = new SessionSecurityToken(transformedPrincipal, TimeSpan.FromHours(8));
            sessionSecurityToken.IsReferenceMode = true;

The downside is that the cookie is linked to server, i.e. we have to deal with server-affinity; if the app pool is refreshed, e.g. when you deploy your web app, or reset IIS, then the claims will be lost. Also, this solution does not work out of the box in the case of web farms. A way to solve this is to introduce your own implementation of SessionSecurityTokenCache (Read on MSDN) to connect to another server dedicated to caching, such as AppFabric caching.

This post discussed the properties of the authentication session. In the next post we’ll look at claims-based authorisation in MVC4.

You can view the list of posts on Security and Cryptography here.

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

32 Responses to Claims-based authentication in MVC4 with .NET4.5 C# part 2: storing authentication data in an authentication session

  1. nickdarvey says:

    Hi, thanks for the awesome series.

    Quick question: I’m using ACS so users get redirected to authenticate from this in web.config:

    Which means I don’t have an AccountsController to put:
    ClaimsAuthenticationManager authManager = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.ClaimsAuthenticationManager;
    authManager.Authenticate(string.Empty, claimsPrincipal);

    (and so I also get my ClaimsPrincipal from ACS).
    Where should I be putting it instead?

  2. nickdarvey says:

    Never mind!
    I hooked into the “WSFederationAuthenticationModule_SignedIn;” event in global.asax.cs and just put them there!

    • Andras Nemes says:

      Hi Nick,

      Thanks for your comments.
      I was going to tell you to read the rest of the series. I’m glad you’ve found the answer yourself.
      //Andras

      • George says:

        Hello, I have windows authentication for MVC web application. You said “Go to Global.asax and comment out the Application_PostAuthenticateRequest() method. We don’t want to run the auth logic upon every page request, so this is redundant code.”

        I don’t have an AccountsController to put: ClaimsAuthenticationManager authManager = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.ClaimsAuthenticationManager;
        authManager.Authenticate(string.Empty, claimsPrincipal)

        Where can I run ClaimsAuthenticationManager? Where do I get the windows authenticated claimsPrincipal and pass it into authManager.Authenticate() method?

        Thank you very much! You wrote an excellent post!!!

        George

  3. Bunmi says:

    I am having problem deploying into microsoft Azure. The project works fine on my local IIS after deploying. On Azure when i log on i get a claim error. Pls note i have no certificate yet but the site is configure to runn ssl. Is the certificate the issue

    Value cannot be null.
    Parameter name: value

    Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

    Exception Details: System.ArgumentNullException: Value cannot be null.
    Parameter name: value

    Source Error:

    An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.

    Stack Trace:

    [ArgumentNullException: Value cannot be null.
    Parameter name: value]
    System.Security.Claims.Claim..ctor(String type, String value, String valueType, String issuer, String originalIssuer, ClaimsIdentity subject, String propertyKey, String propertyValue) +10600157
    System.Security.Claims.Claim..ctor(String type, String value) +34
    Jobz.Controllers.AccountController.GetClaims(String userName, String authenticationType) in c:\Users\Bunmi\Desktop\Jobz\Jobz\Controllers\AccountController.cs:187
    Jobz.Controllers.AccountController.Login(LoginModel model, String returnUrl) in c:\Users\Bunmi\Desktop\Jobz\Jobz\Controllers\AccountController.cs:173
    lambda_method(Closure , ControllerBase , Object[] ) +147
    System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters) +14
    System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters) +182
    System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +27
    System.Web.Mvc.Async.c__DisplayClass42.b__41() +28
    System.Web.Mvc.Async.c__DisplayClass8`1.b__7(IAsyncResult _) +10
    System.Web.Mvc.Async.WrappedAsyncResult`1.End() +50
    System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult) +32
    System.Web.Mvc.Async.c__DisplayClass39.b__33() +58
    System.Web.Mvc.Async.c__DisplayClass4f.b__49() +225
    System.Web.Mvc.Async.c__DisplayClass4f.b__49() +225
    System.Web.Mvc.Async.c__DisplayClass37.b__36(IAsyncResult asyncResult) +10
    System.Web.Mvc.Async.WrappedAsyncResult`1.End() +50
    System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethodWithFilters(IAsyncResult asyncResult) +34
    System.Web.Mvc.Async.c__DisplayClass2a.b__20() +24
    System.Web.Mvc.Async.c__DisplayClass25.b__22(IAsyncResult asyncResult) +99
    System.Web.Mvc.Async.WrappedAsyncResult`1.End() +50
    System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeAction(IAsyncResult asyncResult) +27
    System.Web.Mvc.c__DisplayClass1d.b__18(IAsyncResult asyncResult) +14
    System.Web.Mvc.Async.c__DisplayClass4.b__3(IAsyncResult ar) +23
    System.Web.Mvc.Async.WrappedAsyncResult`1.End() +55
    System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) +39
    System.Web.Mvc.Async.c__DisplayClass4.b__3(IAsyncResult ar) +23
    System.Web.Mvc.Async.WrappedAsyncResult`1.End() +55
    System.Web.Mvc.Controller.EndExecute(IAsyncResult asyncResult) +29
    System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.EndExecute(IAsyncResult asyncResult) +10
    System.Web.Mvc.c__DisplayClass8.b__3(IAsyncResult asyncResult) +25
    System.Web.Mvc.Async.c__DisplayClass4.b__3(IAsyncResult ar) +23
    System.Web.Mvc.Async.WrappedAsyncResult`1.End() +55
    System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +31
    System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +9
    System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +9629296
    System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155

    • Andras Nemes says:

      I’m not sure, I don’t have much experience with Azure, but my first guess that the problem is not due to a certificate issue.
      As you see from the stacktrace the Claim object constructor is missing the ‘value’ parameter as it is null: System.Security.Claims.Claim..ctor(String type, String value) +34. So e.g. new Claim(“Name”, null) will throw an ArgumentNullException. Are you sure that the claims are retrieved correctly in your code?

  4. Thomas says:

    First of all: Thank you, thank you, thank you!!! I know by myself how much time it takes to write proper documentations and what you provide on your side is amazing!

    Now to my question:
    Is it possible that clearing the cookie with
    FederatedAuthentication.SessionAuthenticationModule.SignOut();
    is not the only way?

    I have set up a federated service with a Thinktecture Identity Server. For logout I use the following in my LogOff() method:
    ———————————————————————————————————————
    WSFederationAuthenticationModule authMod = FederatedAuthentication.WSFederationAuthenticationModule;

    //Clear cookie
    authMod.SignOut(false);

    // Initiate signout request to STS
    SignOutRequestMessage soReqMsg = new SignOutRequestMessage(new Uri(authMod.Issuer), authMod.Realm);

    return new RedirectResult(soReqMsg.WriteQueryString());
    ———————————————————————————————————————-
    When logged in I have a cookie FedAuth (probably from IDSrv) and FedAuth1 (probably from my local site). After running LogOff both cookies have been removed, so it seems the additional session module logout is not necessary anymore. Am I right?

    • Andras Nemes says:

      If you check this post and the one after that then you’ll find the updated SignOut message as used with an STS.

      With no log-off on the STS side:

      FederatedAuthentication.WSFederationAuthenticationModule.SignOut();
      

      With log-off on the STS side:

      WSFederationAuthenticationModule authModule = FederatedAuthentication.WSFederationAuthenticationModule;
       
      //clear local cookie
      authModule.SignOut(false);
       
      //initiate federated sign out request to the STS
      SignOutRequestMessage signOutRequestMessage = new SignOutRequestMessage(new Uri(authModule.Issuer), authModule.Realm);
      String queryString = signOutRequestMessage.WriteQueryString();
      return new RedirectResult(queryString);
      

      …which is basically the same as what you’ve written, so yes, that’s fine.
      FedAuth and FedAuth1 belong to the same cookie overall, but a single FedAuth can only store 2KB, therefore they are from the same source. The portion that does not fit into FedAuth is copied to FedAuth1.
      Gruss,
      Andras

  5. Dima says:

    I’m having some trouble figuring this out. In the sessionsecuritytokenreceived event I have
    ClaimsPrincipal.Current.FindAll(“Email”)
    Count = 0
    but if I look at the session token
    e.SessionToken.ClaimsPrincipal.FindAll(ClaimTypes.Email)
    Count = 1

    Why are the two object differ? How can I sync the two? The sessiontoken has the correct claims that I have added in the Authenticate method.

    http://stackoverflow.com/questions/20225158/transforming-claims-with-windows-azure-active-directory-waad

    • Andras Nemes says:

      Hi Dima,
      Have you checked the full string value of ClaimTypes.Email? It is “http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress”, not just “Email”.

      The line:

      ClaimsPrincipal.Current.FindAll(“http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress”);

      should yield Count = 1.

      //Andras

      • Dima says:

        Andras, you’re right there’s a mistake in my code. I’ve changed the sample to say ClaimTypes.Email for both test cases and i’m still not able to keep the claims.What’s interesting is that it’s not just the claims but ClaimsPrincipal.Identity that’s being dropped (this happens on refresh or when I navigate to another view in the site).

        ClaimsPrincipal.Current.FindAll(ClaimTypes.Email)
        Count = 0
        e.SessionToken.ClaimsPrincipal.FindAll(ClaimTypes.Email)
        Count = 1
        [0]: {http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress: dima@dima.com}

  6. Stephan says:

    Hei Andras,

    I have a problem with the cookies and the “remember me” functionality. when a I select “Remember Me” on my login screen everything works fine. But when I close the app and come back later, I am logged in, but the user identity has no claims anymore and hence can’t see anything. any idea what the problem could be?

    • Andras Nemes says:

      Hej Stephan,

      That’s strange, the claims should be preserved of course. I’ve just run the following test:

      1. Start the MVC app
      2. Click the About link
      3. I’m redirected to the ThinkTecture log on page
      4. I check the Remember Me option and log in
      5. I’m redirected to the About page and see the “secret code”
      6. I close web browser but let the MVC app run in VS
      7. I open another browser and navigate to the MVC app on localhost:xxxx
      8. I’m automatically logged in, I can see Hello andras in the top right hand corner
      9. I click on the About page and I can see the secret message

      Did you do anything different? So you are logged in, see “Hello [your name]” on the screen but the claim set is empty, correct? And you cannot see the secret message on the About page, right? Check the following:

      • Leave the SessionAuthenticationModule_SessionSecurityTokenReceived event handler method empty
      • Set a breakpoint in this empty method so that you can inspect the SessionSecurityTokenReceivedEventArgs object
      • Set additional breakpoints within CustomClaimsTransformer.Authenticate, CustomClaimsTransformer.CreateSession and CustomClaimsTransformer.DressUpPrincipal
      • Run the MVC app – you’ll land on the home page, none of the breakpoints will be hit
      • Click the About page – you’re not authenticated, so you’ll get to the idsrv login page
      • Log in and code execution should stop at CustomClaimsTransformer.Authenticate
      • Continue stepping through the code. Make sure you set a long time period for the SessionSecurityToken object
      • The principal’s claim set will be set in the DressUpPrincipal method and then the auth session will be started in the CreateSession method
      • Finally the SessionAuthenticationModule_SessionSecurityTokenReceived event fires – inspect the SessionSecurityTokenReceivedEventArgs object in Visual Studio. It will have a SessionToken.ClaimsPrincipal property. Check if all the claims are present that are supposed to be there
      • Let the code run complete and close the browser, but don’t stop the application in VS
      • Open a new browser window and navigate to the localhost address of the MVC app
      • The SessionAuthenticationModule_SessionSecurityTokenReceived event handler should fire immediately
      • Again inspect the SessionSecurityTokenReceivedEventArgs object to see if all claims are still in there – they should be

      If the claims are not present then I don’t think it’s the fault of the RememberMe option. You are still logged on as far as the auth server is concerned. It is not up to the auth server to maintain the auth session and the app-specific claims within the MVC app. There’s probably something wrong when the auth session is started in the MVC app. For some reason the security token may be wiped out or is not set correctly when FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie is called in CreateSession.

      Before the WriteSessionTokenToCookie is called please verify that the SessionSecurityToken object is set up correctly, i.e. the ClaimsPrincipal has all the claims and the duration is not set accidentally set to some very short time period.

      Can you write a short summary of what you see as the code is executed according to the above steps?

      //Andras

      • Stephan says:

        Hei Andras,

        thanks for the extensive answer.

        One thing I do different is that I stop the MVC app in VS.

        My steps are:
        * logging in with Remember Me option
        * I can see my secret message
        * I close the browser window, which terminates the VS debugger but the app itself is still running and accessible via browser
        * I open another browser window and navigate to localhost
        * I can still see the secret message – so this works so far
        * I restart the VS debugger
        * The app is opened and tells me that I am still logged in (I can see “hello”)
        * I cannot see my secret message

        I stepped through the code and can see that the token is issued correctly (for 8 hours). The tokenReceived event is fired as well. Everything seems alright.

        After restarting the app, the breakpoints in any of the functions of my CustomClaimsTransformer are not hit. no TokenReceived event is raised.

        So, somehow the normal authentication scheme works, but not the ClaimsAuthentication. Is that by design?

      • Andras Nemes says:

        Hej Stephan,

        When you kill the app in Visual Studio then apparently even the auth session dies. The idserver still remembered you as it is up and running in IIS and has not been stopped. Hence you were still logged in but the app-specific claims were lost. However, I tested the following:

        1. Deployed the MVC app to my local IIS
        2. Navigated to the MVC homepage, clicked About, logged in and saw the secret message
        3. In IIS I restarted the application pool that the MVC app runs on
        4. I closed the browser tab with the MVC app and opened a new one
        5. I navigated to the MVC app again and clicked About – I got in automatically and could see the secret message

        So it looks like the built-in IIS Express started by Visual Studio is not handling the restart scenario but “real” IIS does.

        If you want to test on your IIS as well then don’t forget to update the realm and audienceUri values in web.config + realm in the admin section of the ThinkTecture idsrv.

        //Andras

  7. Stephan says:

    ok – my deployment area is not ready yet, so I am not able to test it. But I already suspected sth like this. Anyway, do you have an idea how I can hook into the normal authentication mechanism and dress up my principal? this is more for personal purposes while debugging to avoid entering the details each time.

  8. Arif says:

    I have a sharepoint website, which I have configured with WIF (window indentity foundation) , now my aim is to use Single Sign On (SSO) between my asp.net application and sharepoint, When I try to click on the sharepoint link inside my asp.net application it works and did not show me the login screen of the sharepoint site, but if some body click again that sharepoint link from the asp.net application it show me the login page of the sharepoint site, until I am signout from the sharepoint site.

    Below is the code I am using to do Single Sign On (SSO) from my asp.net application.

    Uri uu = new Uri(“http://sharepoint2010n:1001/default.aspx?wa=wsignin1.0&wtrealm=http://sharepoint2010n:8003/_trust/&wctx=http://sharepoint2010n:8003/_layouts/Authenticate.aspx?Source=%252F&wreply=http://sharepoint2010n:8003/_trust/default.aspx”);
    SignInRequestMessage requestMessage = (SignInRequestMessage)WSFederationMessage.CreateFromUri(uu);

    if (User != null && User.Identity != null && User.Identity.IsAuthenticated)
    {

    SecurityTokenService sts = new CustomSecurityTokenService(CustomSecurityTokenServiceConfiguration.Current);

    SignInResponseMessage responseMessage = FederatedPassiveSecurityTokenServiceOperations.ProcessSignInRequest(requestMessage, User, sts);

    string s = responseMessage.WriteFormPost();
    this.Response.Write(s);

    }
    else
    {
    throw new UnauthorizedAccessException();
    }

  9. Alok says:

    Hello,
    I have implemented both the authentication manager and the authorization manager in my application by following your articles.
    In the authentication manager i transform my incoming claims from active directory into custom claims and store it in the session security token as described.
    Then in the authorization manager i override the checkaccess method to verify if the user has the permission to view the page.
    However, the authorization context doesnt show the claims that were stored in the token. ( and they probably wont, as they were processed in the autehentication manager)
    My question is, is there anyway to have the transformation be done in authorization process so that the context has it?
    (If i do the transformation each time before calling the checkaccess method as shown in the authorization article, then it adds overhead when each page is loaded)
    Please let me know incase there is a way of doing the same.

    Thanking you,
    Alok

    • Andras Nemes says:

      Hi Alok,

      The CreateSession method should store the first auth + transformation result in a session. CustomClaimsTransformer.Authenticate should not be called every time the same resource is accessed. Where do you see that the claims transformation is carried out multiple times for the same resource? Or have I misunderstood your question?
      //Andras

  10. Zinov says:

    Hi Andras:
    Please can you take a look at this question on Stackoverflow and let me know your comments please.

    http://stackoverflow.com/questions/30196008/how-can-i-configure-claim-authentication-in-a-separate-asp-net-mvc-authenticatio

    Thanks
    Zinov

  11. Kit Hill says:

    These blog posts are so helpful! Thank you Andras.

    Here is a small issue that cost me some time. I’m sharing the simple solution in case it would help anyone else who is, like myself, following this series using the Community edition of Visual Studio 2013.

    In the demo, when you register the module in web.config , Andras shows code that includes the line

    Perfectly good XML, right? Yet in my VS setup this line caused three errors to be thrown, stopping the app in its tracks. I looked again and again for typos and so on without finding any.

    Eventually, when I looked at parallel examples in MSDN, I noticed that they used the XML variant syntax

    I edited my code to match and the errors went away. Holy smokes!!

  12. Kit Hill says:

    These blog posts are so helpful! Thank you Andras.

    Here is a small issue that cost me some time. I’m sharing the simple solution in case it would help anyone else who is, like myself, following this series using the Community edition of Visual Studio 2013.

    In the demo, when you register the module in web.config <system.webServer></system.webServer>, Andras shows code that includes the line:
    <add name=”SessionAuthenticationModule” type=”…”></add>

    Perfectly good XML, right? Yet in my VS setup this line caused three errors to be thrown, stopping the app in its tracks. I looked again and again for typos and so on without finding any.

    Eventually, when I looked at parallel examples in MSDN, I noticed that they used the XML variant syntax:
    <add name=”SessionAuthenticationModule” type=”…”/>

    I edited my code to match and the errors went away. Holy smokes!!

  13. Tim S says:

    Hi Andras,
    Thanks for writing such a great blog with useful information and practical examples. I wish I had found your blog earlier.
    I have followed your example in Part 1 of this article to implement Claims Based Authentication in a Web forms application targeting .Net 4.5.1 using Windows Authentication to authenticate the user but then loading user Roles from a SQL Database. These roles are then used to determine which pages a user can access.
    When I have tried to implement part 2 of your article to make it more efficient, I added this code:

    private void CreateSession(ClaimsPrincipal transformedPrincipal)
    {
    SessionSecurityToken sessionSecurityToken = new SessionSecurityToken(transformedPrincipal, TimeSpan.FromHours(8));
    FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionSecurityToken);
    }

    The FederatedAuthentication reference was not recognized. So I installed the Windows Identity Foundation NuGet Pacakge. This has created a conflict converting between System.IdentityModel.Tokens.SessionSecurityToken and Microsoft.IdentityModel.Tokens.SessionSecurityToken. Can you advise how to resolve this? Or is there another way to cache my transformed Principal without using FederatedAuthentication?
    I look forward to your response.
    Tim

    • Andras Nemes says:

      Hi Tim, to be honest I’m not sure about the solution here. The framework has evolved since I published this series and I haven’t had the chance to check out all the changes. //Andras

      • Tim S says:

        I have solved this particular issue. It seems I was using a mixture of old and new framework namespaces. This article helped me sort it out. https://msdn.microsoft.com/en-us/library/jj157091.aspx.
        Now the issue I have since adding session based security is that the first time the checkAccess method in my Authorization Manager executes it fails because the AuthorizationContext doesn’t have the transformed Principal but subsequent calls do have the transformed Principal and it works. Any ideas why the first access has the wrong principal?
        Thanks,
        Tim

    • Sergiy says:

      Hello Tim.
      Did you come up with something to solve that issue? I’m also trying to find a way not to query database table with roles in every request. Did your caching to cookie work?
      Thanx, Sergiy

      • Tim S says:

        Yes, I have fixed it. I had to set Thread.CurrentPrincipal and HttpContext.Current.User to my transformedPrincipal in my Authenticate Method.
        It needs more testing but so far it works well and only does authenticate and transformPrincipal once per session.

    • Sergiy says:

      Hi Tim

      Where from do you call Authenticate method? Many posts and articles call it from Application_PostAuthenticateRequest in Global.asax, but that happens with each and every page request and database lookup becomes very problematic. Could you show how you check your cookie after first request?

      sergiy.bogdancev (at) gmail.com

      Thanx, Sergiy

    • Sergiy says:

      Ah, don’t worry, just make it working myself.

Leave a comment

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

Bite-size insight on Cyber Security for the not too technical.