March 21, 2013 at 3:16 PM

This is the second part in Using ACS and WAAD with JWT Tokens for Web and Store Applications.

If you missed Part 1 make sure you catch up first!

We will first create a Windows Store Application. Then we will make use of the AuthenticationBroker in order to authenticate with our ACS namespace.
To support this type of authentication we will modify our existing MVC application Web Api Controller (created in part 1).

1. Creating the Windows Store Application


If you don't have any experience building Windows Store Applications, there are some things you need to keep in mind:
    - To run / simulate a Windows Store Application, you will need a non-admin user in order to view your application.
    - There is an extra "Simulator" in visual studio that simulates the application instead of deploying it to your Metro interface.


windows_store_vs_simulator

 

1.1. Creating the new project


create_windows_store_project


Add a new javascript file to the solution named "authentication.js". Add the javascript reference to the default.html page.

<script src="/js/authentication.js"></script>

Add a new button and text field in the default.html page by replacing “<p>Content goes here</p>” by:

<p style="margin: 20px;"> 
    <button id="bttnAuthenticate">Login using WAAD</button> 
    <button id="bttnCall">Hit MVC without a token</button> 
    <button id="bttnTokenCall">Hit MVC with Jason Web token</button> 
    <br /> 
    <textarea cols="70" rows="10" id="log"></textarea> 
</p>

Let's add an event handler for our button:

Replace args.setPromise(WinJS.UI.processAll()); in the default.js by 
args.setPromise(WinJS.UI.processAll().done(function () { 
    var bttnAuthenticate = document.getElementById("bttnAuthenticate"); 
    var bttnCall = document.getElementById("bttnCall"); 
    var bttnTokenCall = document.getElementById("bttnTokenCall"); 
    bttnAuthenticate.addEventListener("click", bttnAuthenticateClick, false); 
    bttnCall.addEventListener("click", bttnCallClick, false); 
    bttnTokenCall.addEventListener("click", bttnTokenCallClick, false); 
}));

 

1.2. Modifying our existing Auth Api Controller


In the GetToken method we currently have the following:

[AllowAnonymous] 
[HttpPost] 
public HttpResponseMessage GetToken() 
{ 
    var response = Request.CreateResponse(HttpStatusCode.Redirect); 
    response.Headers.Add("Location", "/"); 
    return response; 
}

 

In order to pass the token to a Windows Store Apps we need to adjust the GetToken method to the following;

[HttpPost] 
[AllowAnonymous] 
public HttpResponseMessage GetToken() 
{ 
    var response = Request.CreateResponse(HttpStatusCode.Redirect); 
    var bootstrapToken = ExtractBootstrapToken(); 
    const string mvcCookieName = "tokenCookie"; 
    // We check for cookies here. If there is no "login" cookie we do not issue a token redirect (for use in Windows Store Apps) 
    if (HttpContext.Current.Request.Cookies.AllKeys.Contains(mvcCookieName)) 
    { 
        response.Headers.Add("Location", string.Format("/")); 
    } 
    else 
    { 
        response.Headers.Add("Location", string.Format("/auth/end?t={0}", bootstrapToken)); 
    } 
    return response; 
}

protected virtual string ExtractBootstrapToken() 
{ 
    var bc = ClaimsPrincipal.Current.Identities.First().BootstrapContext as BootstrapContext; 
    return  ((JWTSecurityToken) bc.SecurityToken).RawData; 
}

 

Let's explain what's changed:
Whenever a request comes in that triggers an action method on any of our Web Api controllers, we will need to provide a token. Whether this request is coming from our current MVC Application or our Windows Store Application.
When we login to MVC, we need to redirect the user to the homepage.
When we login to Windows Store Application, we need to redirect to a Url that is configured in our store application as return Url for the Authentication Broker. (see later)
The redirection Url will also contain the token as a querystring.

The only way (that I know of) we can do this is by setting a cookie on the client that is included in the request when we login from MVC. The store application will not provide any cookies so we will get our custom return Url.
Setting the cookie is easy by adding the following to our Authentication Controller Login Action:

ControllerContext.HttpContext.Response.Cookies.Add(new HttpCookie("tokenCookie") { Value = "" });

 

1.3. Configuring the authentication broker

 

Great, our superfantastic GUI is all setup and our controller will redirect us to a custom URI. Time to add some javascript to start our login process. To achieve that, we will make use of the Authentication Broker

Add the following code to the authentication.js file:

function bttnAuthenticateClick() { 
    var log = document.getElementById("log");

    var requestUri = "https://[yourAcsNamespace].accesscontrol.windows.net:443/v2/wsfederation?wa=wsignin1.0&wtrealm=[yourRealm]"; 
    // Add the url here that you redirect to in the Auth Api Controller 
    var callBackUri = "[yourRedirectUrl]";

    if (authzInProgress) { 
        log.value = "Authorization already in Progress ..."; 
        return; 
    }

    var startUri = new Windows.Foundation.Uri(requestUri); 
    var endUri = new Windows.Foundation.Uri(callBackUri);

    log.value = "Navigating to: " + requestUri + "\r\n";

    authzInProgress = true;

    Windows.Security.Authentication.Web.WebAuthenticationBroker.authenticateAsync( 
        Windows.Security.Authentication.Web.WebAuthenticationOptions.none, startUri, endUri) 
        .done(function (result) { 
            log.value += "Response Data | Status: " + result.responseData + "|" + result.responseStatus + "\r\n";

            if (result.responseStatus === Windows.Security.Authentication.Web.WebAuthenticationStatus.errorHttp) { 
                log.value += "Error returned: " + result.responseErrorDetail + "\r\n"; 
            }

            if (result.responseData.split('?')) { 
                token = result.responseData.split('?')[1].split('t=')[1]; 
                log.value += "JWT: " + token + "\r\n"; 
            } else {

            } log.value += "JWT token is missing... something went wrong \r\n";

            authzInProgress = false; 
        }, function (err) { 
            log.value += "Error returned by WebAuth broker: " + err; 
            log.value += "Error Message: " + err.message + "\r\n"; 
            authzInProgress = false; 
        }); 
}



Replace the following values

 

1.3.1. Debugging the authentication broker

We all know that things mostly won't work the first try. When the authentication broker doesn't work, you won't get a proper error message.
That's why we first want to check if our reply Url gets hit by adding a breakpoint. If that breakpoint doesn't get hit, there is a big chance you have a misconfiguration in ACS.

The return Url configured in ACS will trigger our MVC application. The MVC application will get us the token and we'll forward the call to a new Url, the Url defined in our Windows Store Application as "callbackUri".
This Url doesn't need to exist, it will only trigger the callback of the authentication broker.

 

1.4. Running the applications

Fire up another instance of Visual Studio (run as administrator) and open our MVC solution we created in part 1. 
Add a breakpoint to the GetToken method of our Auth Api Controller. Debug the MVC Application.
Run the Simulator to start our windows store application. Click the "Login using WAAD" button.
You should be prompted with the following screen


simulator_waad_authentication

After login, we can debug our GetToken() method:

extract_bootstraptoken

When everything went well, our Windows Store App will be able to retrieve the token

reply_from_mvc

 

2. Secure our Mvc Web Api

2.1. Modifying Global.asax for authentication


Whenever a request comes to trigger an action on one of our Web Api Controllers, we need to parse the JWT Token in order to authenticate the user.
Authentication happens in the Global.asax, authorization will happen in the Controller Action by decorating the class or method with an Authorization Attribute.

To intercept a Web Api request we can make use of the Web Api Message Handlers

Add the following to the Global.asax.cs:

public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            Configure(GlobalConfiguration.Configuration);
            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            AuthConfig.RegisterAuth();
        }

        static void Configure(HttpConfiguration config)
        {
            config.MessageHandlers.Add(new JwtTokenValidationHandler());
        }

        internal class JwtTokenValidationHandler : DelegatingHandler
        {
            protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            {
                // We support Forms Based authentication
                var authCookie = request.Headers.GetCookies(FormsAuthentication.FormsCookieName).FirstOrDefault();
                if (authCookie != null)
                {
                    var authC = authCookie[FormsAuthentication.FormsCookieName];
                    ParseCookie(authC.Value);

                    return base.SendAsync(request, cancellationToken);
                }

                // We support JWT Token authentication
                HttpStatusCode statusCode;
                string token;
                if (!TryRetrieveToken(request, out token))
                {
                    // if the token could not get parsed we still allow access to our controller action methods.
                    return base.SendAsync(request, cancellationToken);
                }

                try
                {
                    var tokenHandler = new JWTSecurityTokenHandler();

                    List<SecurityKey> skeys;

                    var identityConfig = new IdentityConfiguration();
                    string keyName = identityConfig.AudienceRestriction.AllowedAudienceUris[0].ToString();

                    if (((NamedKeyIssuerTokenResolver)(identityConfig).IssuerTokenResolver).SecurityKeys.TryGetValue(keyName, out skeys))
                    {
                        var tok = new NamedKeySecurityToken(keyName, skeys);
                        var validationParameters = new TokenValidationParameters
                        {
                            AllowedAudience = keyName,
                            ValidIssuer = ((ConfigurationBasedIssuerNameRegistry)identityConfig.IssuerNameRegistry).ConfiguredTrustedIssuers.ToList()[0].Value,
                            SigningToken = tok
                        };

                        var currentPrincipal = tokenHandler.ValidateToken(token, validationParameters);

                        Thread.CurrentPrincipal = currentPrincipal;
                    }

                    return base.SendAsync(request, cancellationToken);
                }
                catch (SecurityTokenValidationException)
                {
                    return base.SendAsync(request, cancellationToken);
                }

                catch (Exception ex)
                {
                    statusCode = HttpStatusCode.InternalServerError;
                    // TODO log exception
                }
                return Task<HttpResponseMessage>.Factory.StartNew(() =>
                                                                  new HttpResponseMessage(statusCode));
            }

            private static void ParseCookie(string cookieValue)
            {
                // Parse authentication Cookie here
                FormsAuthenticationTicket enabledTicket = FormsAuthentication.Decrypt(cookieValue);

                if (enabledTicket == null) return;

                var id = new FormsIdentity(enabledTicket);
                var principal = new GenericPrincipal(id, null);
                HttpContext.Current.User = principal;
            }

            private static bool TryRetrieveToken(HttpRequestMessage request, out string token)
            {
                token = null;

                // Substract the token from the bearer authorization header
                IEnumerable<string> authzHeaders;
                if (!request.Headers.TryGetValues("Authorization", out authzHeaders) || authzHeaders.Count() > 1)
                {
                    return false;
                }
                var bearerToken = authzHeaders.ElementAt(0);
                token = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;
                return true;
            }
        }
    }

 

You don't need to change anything, it will retrieve everything all security settings from the Web.Config.
What we try to do here is set a CurrentPrincipal when we can. When we find an authentication cookie, we parse that cookie (we support forms authentication on MVC). When we find an authentication header, we retrieve the token from the header and try to validate it. If it's valid, we know the current user.
Of course it can happen that the cookie or the authentication header is invalid. However, as we are only authenticating here, we still let the call go through to the controller action. Our controller will check if the user is authenticated or not. Why do we do this? Well, we still need some anonymous actions to be accessible for unauthenticated users.

 

2.2. Adding a Web Api AuthorizationAttrubute

We already added a MultipleAuthorization Attribute for our normal Controllers. Now it's time to add an ApiAuthorizationAttribute for our Api Controllers.

Let's create a new class called "ApiAuthorization" and add the following code

using System.Security.Principal; 
using System.Threading; 
using System.Web.Http.Controllers;

namespace Codit.Blog.Waad.Mvc.Infrastructure.Auth 
{ 
    public class ApiAuthorizeAttribute : System.Web.Http.AuthorizeAttribute 
    { 
        protected override bool IsAuthorized(HttpActionContext context) 
        { 
            IPrincipal client = Thread.CurrentPrincipal; 
            return client.Identity.IsAuthenticated; 
        } 
    } 
}

 

We also add a new Api Controller called "QuoteController" with the following code:

using System.Web.Http; 
using Codit.Blog.Waad.Mvc.Infrastructure.Auth;

namespace Codit.Blog.Waad.Mvc.Controllers.api 
{ 
    public class QuoteController : ApiController 
    { 
        [ApiAuthorize] 
        public string GetSecretQuote() 
        { 
            return "Housework can't kill you, but why take a chance?"; 
        } 
    } 
}

 

We implement our other buttons onclick event handlers:

function bttnCallClick() { 
    WinJS.xhr({ 
        url: 'http://localhost:50238/api/quote/GetSecretQuote', 
        type: 'GET' 
    }).then( 
        function (response) { 
            var log = document.getElementById("log"); 
            log.value = "bttnCall response \r\n"; 
            var json = JSON.parse(response.responseText); 
            log.value += json; 
        }, 
        function(error) { 
            var log = document.getElementById("log"); 
            log.value = "Error in call " + error; 
        }, 
        function (progress) { 
        } 
); 
}

Basically we're requesting the quote but as we're not logged in, our ApiAuthorization class will revoke authorization.

hit_mvc_without_jwt

 

function bttnTokenCallClick() { 
    WinJS.xhr({ 
        url: 'http://localhost:50238/api/quote/GetSecretQuote', 
        type: 'GET', 
        headers: { 
            "Authorization": "Bearer " + token 
        } 
    }).then( 
        function(response) { 
            var log = document.getElementById("log"); 
            log.value = "bttnCall response \r\n";

            var json = JSON.parse(response.responseText); 
            log.value += json; 
        }, 
        function(error) { 
            var log = document.getElementById("log"); 
            log.value = "Error in token call " + error; 
        }, 
        function(progress) { 
        } 
    ); 
};

 

Here we add the token to our Authorization header so it will get parsed in our Global.asax file.
hit_mvc_with_jwt

 

We could extend this example to add more in depth user role authentication but we simply need to call the GraphApi for this.

I hope you enjoyed this series. As you have already seen, there is a lot of configuration to do in order to configure ACS and WAAD. But, with a lot of trial and error you will get a better understanding on how things work.

Download part 2 of this series, the zip contains the Windows Store Application and an updated MVC Application.

Posted in: Azure | WIF

Tags: , ,


March 19, 2013 at 3:39 PM

The purpose of this blog post is to give you a complete overview on how to configure Windows Azure Active Directory (WAAD) together with Windows Azure Access Control Service (ACS). We will create 2 applications (an MVC 4 Web application and a Windows Store Application).

In our MVC application we will support Forms Based Authentication and Claims Based Authentication at the same time.

In our Windows Store Application we will use the JWT token generated by ACS to authenticate on our previously created MVC Web Api controllers.

On both applications we will be able to authorize users based on their roles (CIA users and U.S. Marine users).

 

This blog post will consist of 2 parts:

1. Setting up ACS, WAAD and an MVC Application by using Jason Web Tokens (this part)

2. Add support for Windows Store Applications by passing the Jason Web Token

 

1. Configuration on Windows Azure

Prerequisites

  • .Net Framework 4.5 (Visual Studio 2012)
  • Azure subscription
 

1.1 Configuring WAAD

1. Create / or use an existing WAAD account (our sample WAAD domain is "natsec"). Sign-up: (https://activedirectory.windowsazure.com/Signup/QuickSignup.aspx?ru=https://activedirectory.windowsazure.com&ali=1)

windows_azure_active_directory_create

2. Login on https://activedirectory.windowsazure.com/

3. From here it is pretty straightforward. You can sync an existing AD with your WAAD or create users / groups manually.

For our sample, I've created the following

            2 groups
                - Central Intelligence Agency 
                - US Marine


            3 users:
                - David Estes (member of Central Intelligence Agency group)
                - Saul Goodman (member of Central Intelligence Agency group)
                - Mike Faber (member of US Marine group)


        windows_azure_active_directory_groups windows_azure_active_directory_users     

 

1.2. Configuring ACS

1. Login to your current Windows Azure Subscription (https://manage.windowsazure.com) 
2. Navigate to Active Directory. Create a new Access Control Namespace and after creation, click on the management link (https://[your_namespace].accesscontrol.windows.net/v2/mgmt/web).

 

1.2.1. Create a new Identity Provider

1. Select WS-Federation Identity provider, click next.

2. Enter a display name, select the Url radio button for WS-Federation metadata and enter

https://accounts.accesscontrol.windows.net/[your_namespace].onmicrosoft.com/FederationMetadata/2007-06/FederationMetadata.xml 

In the above url replace [your_namespace] and browse to it to make sure it works.


azure_access_control_service_identity_provider

 

1.2.2. Create a new Relying Party Application

1. As Realm you type your application realm (free to choose, but must be unique in your ACS). ex. urn:NatSec

2. As Return Url, you enter the Url that your MVC front-end will run on. You can change this later to match with your local IIS Express port. The subpath of this URL will be discussed later in this post (needed to set our cookies and/or JWT token).

3. As Token Format we will choose JWT (Jason Web Token: at time of writing it is still in beta, but currently the most compact token available).

4. Under Authentication Settings we select our Identity Provider we just created and check "Create new rule group".

5. For Token Signing Settings we opt for a new generated Symmetric Key. We work with a symmetric key as a certificate would require the MVC website to have it installed in the Certificate Store. As we will host the website on Windows Azure Websites, we currently can't add the certificate.

azure_access_control_service_add_relying_party

 

 

1.2.3. Changing Default Rule Group

In ACS you can map WAAD Claims to your custom claims. To distinguish user access in our application we could map WAAD User Roles to custom User Roles we can use in our application (Another relying party could map role claims to our same custom format). The only problem we face is that the roles are not included in the claims (claims could become very large). When we need to request the current user roles, we need to query the GraphApi from WAAD. The mapping of user roles will happen on our MVC backend later.

1. Edit the Rule Group and Generate default rules. (this way we will receive standard claims like 'firstname', 'lastname', ... )

In order to please ASP.Net Security we will need to modify an existing claim. ASP.Net AntiForgery expects the following claims to be present:

2. Edit the name claim and change the output claim to "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" (in the predefined selectable types)

change_input_claim

 

1.3. Creating a Trust relation ship between ACS and WAAD

 

Prerequisites

 

WAAD should be configured to trust our ACS to issue a token. The only way we currently can configure this is with PowerShell.

Enter the following statements

connect-msolservice 
import-module msonlineextended –force 
$replyUrl = New-MsolServicePrincipalAddresses -Address "https://[yournamespace].accesscontrol.windows.net/" 
New-MsolServicePrincipal -ServicePrincipalNames @("https://[yournamespace].accesscontrol.windows.net/") -DisplayName "[justAName]" -Addresses $replyUrl

You will be prompted for your WAAD credentials after line 1 (ex. david.estes@natsec.onmicrosoft.com).

On line 3 you'll need to replace [yournamespace] with the namespace you choose for ACS (ex. coditblog)

On line 4 you'll need to replace [yournamespace] with the namespace you choose for ACS (ex. coditblog) and [justAName] with a descriptive name).

waad_acs_trust

Be sure to note the symmetric key credential that is created for you as output of the cmdlet if you do not supply a value. Once the service principal has been created, you cannot retrieve the symmetric key credential. You will need to use it in your code when authenticating to Windows Azure AD Access Control.

 

2. Building our MVC Application

 

2.1. Creating the MVC Application

The MVC Application will act as an API to issue a token for our Windows Store Application. It will contain all configuration in order to authenticate with ACS. 
Create a new MVC Application using an Internet Application as Project Template. 

vs2012_mvc_waad_create_app vs2012_mvc_waad_create_app_project_template 

Most existing applications will already have some security mechanism, so we'll jump into a real-world scenario and use Forms Authentication as our current authentication. We will extend our authentication mechanism by configuring ACS in our application. 
Using the Internet Application Template, Forms Authentication is already configured. Forms Authentication in MVC 4 is no longer using the ASP.Net MembershipProvider but instead it's using SimpleMembership.


        First of all, start the application and Create a new users (default password in the sample is: "mypassword")
        mvc_forms.create_user


Stop the application and click "Show All Files" in Solution Explorer. You will notice the mdf ASP.net Membership database file under "App_Data". Include the file into the project and double click on it.


        For the sake of simplicity, we'll create our roles using T-SQL. Right click the database (in Server Explorer) and select "New Query".
        Execute the following statement:

insert into [aspnet-Codit.Blog.Waad.Mvc-20130301113648].[dbo].[webpages_Roles] (RoleName) values ('CIA') 
insert into [aspnet-Codit.Blog.Waad.Mvc-20130301113648].[dbo].[webpages_Roles] (RoleName) values ('U.S. Marine') 
insert into [aspnet-Codit.Blog.Waad.Mvc-20130301113648].[dbo].[webpages_UsersInRoles] (UserId, RoleId) values (1, 2)

        mvc_simplemembership_configure_roles


        As you may have noticed already, the roles are different from the roles we previously configured in WAAD. This is on purpose.

 

2.2. Configuring ACS in our MVC Application

Add the following NuGet Package to your solution: "JSON Web Token Handler For the Microsoft.Net Framework 4.5" (Id: Microsoft.IdentityModel.Token.JWT (https://nuget.org/packages/Microsoft.IdentityModel.Tokens.JWT/0.1.0))

Also add a reference to System.IdentityModel (v4.0.0.0) and System.IdentityModel.Services (v4.0.0.0)


        Create a new class "CustomJwtSecurityTokenHandler"
 

using System.Collections.Generic; 
using System.IdentityModel.Tokens; 
using System.Linq; 
using System.Security.Claims; 
using Microsoft.IdentityModel.Tokens.JWT;

namespace Codit.Blog.Waad.Mvc.Infrastructure.Auth.Handlers 
{ 
	public class CustomJwtSecurityTokenHandler : JWTSecurityTokenHandler 
	{ 
		public override ClaimsPrincipal ValidateToken(JWTSecurityToken jwt, TokenValidationParameters validationParameters) 
		{ 
			if ((validationParameters.ValidIssuer == null) && 
				(validationParameters.ValidIssuers == null || !validationParameters.ValidIssuers.Any())) 
			{ 
				validationParameters.ValidIssuers = new List<string> { ((ConfigurationBasedIssuerNameRegistry)Configuration.IssuerNameRegistry).ConfiguredTrustedIssuers.First().Value }; 
			}

			if (validationParameters.SigningToken == null) 
			{

				var resolver = (NamedKeyIssuerTokenResolver)this.Configuration.IssuerTokenResolver; 
				if (resolver.SecurityKeys != null) 
				{ 
					var keyName = Configuration.AudienceRestriction.AllowedAudienceUris[0].ToString(); 
					List<SecurityKey> skeys; 
					if (resolver.SecurityKeys.TryGetValue(keyName, out skeys)) 
					{ 
						var tok = new NamedKeySecurityToken(keyName, skeys); 
						validationParameters.SigningToken = tok; 
					} 
				} 
			} 
			return base.ValidateToken(jwt, validationParameters); 
		} 
		protected override string NameIdentifierClaimType(JWTSecurityToken jwt) 
		{ 
			return ClaimTypes.GivenName; 
		} 
	} 
}

JWT Token Validation isn't supported out of the box so we override the ValidateToken method. The main issue is that the parameters defined in the config aren't recognized so we need to read them from the config ourselves.

We do an override of the NameIdentifierClaimType method in order to pass our name claim to be set in the RolePrincipal object. By default it will use the "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" claim. This claim contains our user's complete e-mail adress. Instead we want to greet the user with his first name. We return the default nameclaim ClaimTypes.GivenName (="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"). The result of this override is demonstrated in the following screenshot


        waad_claims_roleprincipal

 


        Next add the following configuration to your Web.Config
     

<system.identityModel> 
<identityConfiguration saveBootstrapContext="true"> 
  <audienceUris> 
	<add value="[yourRealm]" /> 
  </audienceUris> 
  <securityTokenHandlers> 
	<add type="[yourClassAndAssemblyCustomJWTTokenHandler]" /> 
	<securityTokenHandlerConfiguration> 
	  <certificateValidation certificateValidationMode="PeerTrust" /> 
	  <issuerTokenResolver type="[yourClassAndAssemblyCustomJWTTokenHandler]"> 
		<securityKey symmetricKey="[jwtSymmetricKey]" name="[yourRealm]" /> 
	  </issuerTokenResolver> 
	</securityTokenHandlerConfiguration> 
  </securityTokenHandlers> 
  <issuerNameRegistry type="System.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> 
	<trustedIssuers> 
	  <add thumbprint="[thumbPrint]" name="https://[yournamespace].accesscontrol.windows.net/" /> 
	</trustedIssuers> 
  </issuerNameRegistry> 
</identityConfiguration> 
</system.identityModel> 
<system.identityModel.services> 
<federationConfiguration> 
  <cookieHandler requireSsl="false" /> 
  <wsFederation passiveRedirectEnabled="true" issuer="https://[yournamespace].accesscontrol.windows.net/v2/wsfederation" realm="[yourRealm]" requireHttps="false" /> 
</federationConfiguration> 
</system.identityModel.services>

On line 2:

We've added saveBootstrapContext="true" in order to retrieve our JWT token easily in our controller.

On line 4:

Replace [yourRealm] with the realm you entered when creating a relying party (ex. urn:NatSec)

On line 7:

Replace [yourClassAndAssemblyCustomJWTTokenHandler] with the your "custom jwt class, assemblyname".

On line 10:

Replace [yourClassAndAssemblyCustomJWTTokenHandler] with the your "custom jwt class, assemblyname".

On line 11:

Replace [yourRealm] with the realm you entered when creating a relying party (ex. urn:NatSec)

On line 11:

Replace [jwtSymmetricKey] with the symmetric key of your JWT (ACS Management page => Certificates and keys => Token Signing => [your RP] (WAAD - NatSec) => Copy the full 256-bit symmetric key

On line 17: 

Replace [thumbPrint] with your Service Certificate Thumbprint (ACS Management page => Service Settings => Certificates and keys => Token Signing => Service Namespace (X.509 Certificate) => Copy your thumbprint).

Replace [yournamespace] with your ACS namespace (ex. coditblog)

On line 25:

Replace [yournamespace] with your ACS namespace (ex. coditblog)

Replace [yourRealm] with the realm you entered when creating a relying party (ex. urn:NatSec)


        Add requestValidationMode="4.5" to the httpRuntime element.
      

<httpRuntime targetFramework="4.5" requestValidationMode="4.5" />


        Add <allow users="*" /> as a child of the <authorization> element and above the <deny users="?" /> element.

<authorization>
  <allow users="*"/>
  <deny users="?" />
</authorization>


        Make sure any wif packages do not disable forms authentication module (comment the remove element if you have it)

<system.webServer>
<modules>
      <!--<remove name="FormsAuthentication" />-->
</modules>
</system.webServer>

 

When you run the application you will be automatically promted with the following screen:  
waad_sign_in

 

 

2.3. Support ACS and Forms Authentication

In order to allow Forms Based login, we need to disable the auto redirect to the Windows Azure Active Directory login page. We can do this by setting "passiveRedirectEnabled" to false in our web.config (wsFederation element). 
When we now run the application we can use the standard Forms Authentication but the WAAD seems to be disabled. We need to manually add a link in order for the user to login using WAAD.
We will and cannot use the build in Authentication Providers class for this demo. Instead locate the following line in Login.cshtml
      

@Html.Action("ExternalLoginsList", new { ReturnUrl = ViewBag.ReturnUrl })


and replace it with

<a href="https://[yournamespace].accesscontrol.windows.net:443/v2/wsfederation?wa=wsignin1.0&wtrealm=[yourRealm]" title="Log in using your Windows Azure Active Directory account">Windows Azure Active Directory</button>

Make sure you replace the following in the above URL

    • [yourRealm] with the realm you entered when creating a relying party (ex. urn:NatSec) 
    • Replace [yournamespace] with your ACS namespace (ex. coditblog)  
 

waad_login_option


        In order to authenticate successfully with our ACS we need to make sure we support the configured Return Url (in ACS).

We will create a new WebApi controller to act as our return Url. You could also use a normal controller but our WebApi controller will become in handy when we also support Windows Store Applications. We use WebAPI for returning tokens and JSON data, we will use normal Controllers for returning Views. Create a new folder named "api" under "Controllers". Add a new Empty Web API Controller named "AuthController".

 

        empty_web_api_controller


Add the following
      

using System.Net; 
using System.Net.Http; 
using System.Web.Http;

namespace Codit.Blog.Waad.Mvc.Controllers.api 
{ 
	public class AuthController : ApiController 
	{ 
		[AllowAnonymous] 
		[HttpPost] 
		public HttpResponseMessage GetToken() 
		{ 
			var response = Request.CreateResponse(HttpStatusCode.Redirect); 
			response.Headers.Add("Location", "/"); 
			return response; 
		} 
	} 
}


When the return url is configured correctly in ACS, then this controller action will be called after the login procedure of WAAD.
It will receive a HttpPost with security tokens, these tokens will result in FedAuth cookies.


Fiddler Return Url request from WAAD:

fiddler_token_response


Fiddler Cookies on Redirect to home page:

fiddler_fedauth_cookies

 

And we're in: 

waad_login_success

 

 

 

2.4. Enabling WAAD Graph Api and Role Authentorization

We've already discussed authentication before, now it is time for authorization. Based on the roles of a user we will allow or deny page requests.

 

2.4.1. References and Packages

Add the following NuGet Package Windows Azure Authentication Library (Beta) to your solution

Id: Microsoft.WindowsAzure.ActiveDirectory.Authentication (https://nuget.org/packages/Microsoft.WindowsAzure.ActiveDirectory.Authentication/0.6.1)

Add a service reference to your WAAD using the following URL: https://graph.windows.net/[yourWaadDomain]

Replace [yourWaadDomain] by your Waad Domain (obviously...) ex. NatSec

Reference following assemblies:

System.Windows.Forms (v4.0.0.0)

 

In order to access the GraphApi, we need to give read-permissions. Fire up a PowerShell instance and enter following commands.

First of all we need to retrieve the RoleMemberObjectId (so it can be replaced in the next command), type the following command:

Get-MsolServicePrincipal | select DisplayName,ObjectId

 

After executing the command below we will have read-only access to the WAAD Directory. 

Add-MsolRoleMember -RoleName "Service Support Administrator" -RoleMemberType ServicePrincipal -RoleMemberObjectId [yourRoleMemberObjectId]

 

2.4.2. Interaction with the GraphApi

Add a new class named "GraphClient" (see inline for detailed explanations)

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Net;

using Codit.Blog.Waad.Mvc.NatSec.Microsoft.WindowsAzure.ActiveDirectory;

using Microsoft.WindowsAzure.ActiveDirectory.Authentication;



namespace Codit.Blog.Waad.Mvc.Infrastructure.Auth.GraphApi

{

    /// <summary>

    /// This class uses the Windows Azure Graph API to retrieve a given user's direct group memberships.

    /// Except for the <see cref="GetUserGroups"/> method, the code in this class is heavily based on public

    /// Graph API examples from Microsoft.

    /// </summary>

    public class GraphClient

    {

        private readonly DirectoryDataService _dataService;

        private readonly string _tenantDomainName;

        private readonly string _servicePrincipalSymmetricKey;

        private readonly string _tenantId;

        private readonly string _appPrincipalId;



        // ReSharper disable InconsistentNaming

        /// <summary>

        /// Name of the HTTP header that contains the unique ID of a client request.

        /// Used when making Graph API requests

        /// </summary>

        private const string HeaderName_ClientRequestId = "client-request-id";



        /// <summary>

        /// Authorization header used when making Graph API requests

        /// </summary>

        private const string HeaderName_Authorization = "Authorization";



        /// <summary>

        /// Version of the data contract, used when making Graph API requests

        /// </summary>

        private const string HeaderName_DataContractVersion = "x-ms-dirapi-data-contract-version";

        // ReSharper restore InconsistentNaming



        private const string DataContractVersion = "0.8";



        public GraphClient(string tenantDomainName, string tenantId, string appPrincipalId, string servicePrincipalSymmetricKey)

        {

            _tenantDomainName = tenantDomainName;

            _tenantId = tenantId;

            _servicePrincipalSymmetricKey = servicePrincipalSymmetricKey;

            _appPrincipalId = appPrincipalId;



            _dataService = new DirectoryDataService(GetConnectionUri());

            _dataService.SendingRequest += (sender, args) =>

            {

                var webRequest = ((HttpWebRequest)args.Request);

                webRequest.Headers.Add(HeaderName_Authorization, GetAuthorizationHeader());

                webRequest.Headers.Add(HeaderName_DataContractVersion, DataContractVersion);

                webRequest.Headers.Add(HeaderName_ClientRequestId, Guid.NewGuid().ToString());

            };

        }



        /// <summary>

        /// Identifier of the issuing resource for the symmetric key.

        /// Used for getting an access token for requests.

        /// Looks something like this:

        /// A4423C7C-35F7-4679-99F8-5921A0482B63@tenantdomain.onmicrosoft.com

        /// (the GUID here is an example, not the actual value)

        /// </summary>

        private string GetSymmetricKeyIssuingResource()

        {

            return _appPrincipalId + "@" + _tenantDomainName;

        }



        /// <summary>

        /// Realm identifier for the service we want to access. Which is the tenant domain

        /// hosted at Windows Azure AD.

        /// 00000002-0000-0000-c000-000000000000/graph.windows.net@yourtenantdomain.onmicrosoft.com

        /// (the GUID here is the actual identifier)

        /// </summary>

        private string GetServiceRealm()

        {

            return AzureAdPrincipalId + "/" + GraphApiServiceHost + "@" + _tenantDomainName;

        }



        /// <summary>

        /// URI of the Graph API we're accessing.

        /// Looks something like this:

        /// https://graph.windows.net/EA314D90-8751-4534-A96E-77A5DF0E72AB

        /// (the GUID here is an example, not the actual value)

        /// </summary>

        private Uri GetConnectionUri()

        {

            return new Uri(string.Format(@"https://{0}/{1}", GraphApiServiceHost, _tenantId));

        }



        /// <summary>

        /// URI of the WAAD tenant domain.

        /// Looks something like this:

        /// https://accounts.accesscontrol.windows.net/yourtenantdomain.onmicrosoft.com

        /// </summary>

        private string GetFullTenantUri()

        {

            return "https://accounts.accesscontrol.windows.net/" + _tenantDomainName;

        }



        /// <summary>

        /// This is the known identifier of the WAAD service principal

        /// </summary>

        private const string AzureAdPrincipalId = "00000002-0000-0000-c000-000000000000";



        /// <summary>

        /// This is the domain name of the WAAD Graph API service

        /// </summary>

        private const string GraphApiServiceHost = "graph.windows.net";



        /// <summary>

        /// Method to get the Oauth2 Authorization header from WAAD. This basically authenticates

        /// the calling application to WAAD and gets a token with which further requests can be made.

        /// </summary>

        private string GetAuthorizationHeader()

        {

            var authContext = new AuthenticationContext(GetFullTenantUri());

            var credential = new SymmetricKeyCredential(GetSymmetricKeyIssuingResource(), Convert.FromBase64String(_servicePrincipalSymmetricKey));

            AssertionCredential assertionCredential = authContext.AcquireToken(GetServiceRealm(), credential);

            return assertionCredential.CreateAuthorizationHeader();

        }



        /// <summary>

        /// Queries the Graph API for security groups in which an active user with the given

        /// <paramref name="userPrincipalName"/> is a direct member.

        /// </summary>

        public IList<Group> GetUserGroups(string userPrincipalName)

        {

            var user = _dataService.Users

                .Where(u => u.AccountEnabled == true && u.UserPrincipalName == userPrincipalName)

                .AsEnumerable()

                .SingleOrDefault();



            var groupReferences = _dataService.LoadProperty(user, "MemberOf")

                .OfType<ReferencedObject>()

                .Where(r => r.ObjectType == "Group")

                .Select(g => g.ObjectId);



            return _dataService.Groups

                .AsEnumerable()

                .Where(g => groupReferences.Contains(g.ObjectId))

                .ToList();

        }

    }

}

 

Add a new class named GraphApiClaimAuthenticationManager (will use the GraphApi class)

Please replace [yourSymmetricKey] and [appPrincipalId] with the values you received when creating your WAAD using powershell.

Please see inline for detailed explanation. 

using System.Collections.Generic;

using System.Linq;

using System.Security.Claims;

using System.Web.Http;

using Codit.Blog.Waad.Mvc.Infrastructure.Auth.GraphApi;



namespace Codit.Blog.Waad.Mvc.Infrastructure.Auth.GraphApi

{

    /// <summary>

    /// This class uses <see cref="GraphClient"/> to read the AD security groups in which a given

    /// <see cref="ClaimsPrincipal"/> is a direct member and then converts the memberships to group

    /// claims. The group claims are then used to authorize user actions with <see cref="AuthorizeAttribute"/>.

    /// </summary>

    public class GraphApiClaimAuthenticationManager : ClaimsAuthenticationManager

    {

        /// <summary>

        /// This is an arbitrary string identifying the claims issued by this class.

        /// It exists to make it possible to distinguish those claims from other claims.

        /// </summary>

        private const string ClaimIssuerName = "waad-demo-usergroup-roleclaim";



        /// <summary>

        /// Augument <paramref name="incomingPrincipal"/> with claims obtained by converting

        /// the user's direct security group memberships to role claims.

        /// </summary>

        public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)

        {

            if (incomingPrincipal == null || !incomingPrincipal.Identity.IsAuthenticated)

                return incomingPrincipal;



            // TODO: Load user roles from cache to avoid roundtrips to GraphApi

            // TODO: Load these keys from persistence store based on the tenantDomain

            const string appPrincipalId = "[yourAppPrincipalId]";

            const string symmKey = "[yourSymmetricKey]";

            var upn = incomingPrincipal.FindFirst(ClaimTypes.Upn).Value;

            var tenantId = incomingPrincipal.FindFirst("http://schemas.microsoft.com/ws/2012/10/identity/claims/tenantid").Value;

            var tenantDomain = upn.Split('@')[1];



            var graphClient = new GraphClient(tenantDomain, tenantId, appPrincipalId, symmKey);

            var userGroups = graphClient.GetUserGroups(upn);



            var localRoles = new List<string>();

            // Our local roles are CIA and U.S. Marine

            // The roles on WAAD are Central Intelligence Agency and US Marine

            // TODO: load these from persistence store

            foreach (var userGroup in userGroups)

            {

                switch (userGroup.DisplayName)

                {

                    case "Central Intelligence Agency":

                        localRoles.Add("CIA");

                        break;

                    case "US Marine":

                        localRoles.Add("U.S. Marine");

                        break;

                }

            }



            var roleClaims = localRoles.Select(x => new Claim(ClaimTypes.Role, x, null, ClaimIssuerName));



            ((ClaimsIdentity)incomingPrincipal.Identity).AddClaims(roleClaims);



            return incomingPrincipal;

        }

    }

}

 

There are a couple of things that can do here in order to extend functionality. If we support multiple WAAD tenants, we could load the securitykeys based on the waad domain from a persistence store.

We could also store mapping roles in our persistence store in order to configure them later. As noted before, roles of WAAD aren't the roles you will use locally.

 

In order to use the GraphApiClaimAuthenticationManager we need to add an extra line in our config that is responsible for kicking in our Authentication Manager

<claimsAuthenticationManager type="[NamespaceAndClassOfAuthManager], [assemblyName]" />

 

We would like to restrict access to our Contact page for US Marines. So only our CIA personnel will be authorized to access the contact page.

Protect our controller by replacing our Controller Action in HomeController.cs. The MultipleAuthorize class decoration is discussed later.

[MultipleAuthorize(Roles = "CIA")]

public ActionResult Contact()

{

	ViewBag.Message = "CIA Only!";



	return View();

}

 

Normally we could make use of the IsInRole method exposed by ClaimsPrincipal. However, I never succeeded on using this when we have Claims Based and Forms Based Authentication enabled. It will always falls back to Forms Based Authentication by default.

Therefore, I've implemented a custom authorization attribute that will check for claims and roles manually.

using System.Linq;

using System.Security.Claims;

using System.Security.Principal;

using System.Threading;

using System.Web.Mvc;



namespace Codit.Blog.Waad.Mvc.Infrastructure.Auth

{

    public class MultipleAuthorize : AuthorizeAttribute

    {

        protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)

        {

            IPrincipal client = Thread.CurrentPrincipal;



            // Customer ClaimsIdentity authorization

            if ((client.Identity.GetType() == typeof (ClaimsIdentity)))

            {

                if (client.Identity.IsAuthenticated)

                {

                    var roleClaims = ((ClaimsIdentity) client.Identity).Claims.Where(x => x.Type == ClaimTypes.Role).ToList();

                    

                    if (Roles.Split(',').Any(role => roleClaims.FirstOrDefault(x => x.Value == role) == null))

                    {

                        return false;

                    }

                    return true;

                }

                return false;

            }



            // Use normal forms authorization     

            return base.AuthorizeCore(httpContext);

        }

    }

}

 

When you login either using WAAD or Forms authentication, you should be able to access the Contact page when you are part of the CIA and get an Access Denied Exception when you aren't part of the CIA.

You can download the complete sample of this first part here

 

In the next part, we'll add support for Windows Store Applications. Stay tuned!

 

Posted in: Azure | WIF

Tags: , ,


February 19, 2012 at 12:26 PM

A while ago I had a little surprise with a demo project.
The project consists of a client application, a claims aware WCF service and AD FS as token issuer.

This demo was working fine until now...
Without changing anything, authentication always fails with this error message (see WIF tracing):

ID4175: The issuer of the security token was not recognized by the IssuerNameRegistry. To accept security tokens from this issuer, configure the IssuerNameRegistry to return a valid name for this issuer.

Offcourse my token issuer certificate was added to the 'trustedissuers' collection on the WCF service, so something must have changed in AD FS.

The signing certificate in AD FS shows this:

As you can see, there are two signing certificates (I had one before). The second signing certificate was created by AD FS automatically because my signing certificate was reaching it's expiration date. This feature - AD FS creating a new self-signed certificate when the old one nears expiration - is called Auto Certificate Rollover.

When using self-signed certificates, auto certificate rollover is on by default.
AF FS will use the signing certificate marked as 'Primary' to sign issued tokens.
The only thing I need to do is adding the new certificate to the trusted issuers collection as shown below:

 

Note: The reason why my sample failed is that I use the out-of-the box 'ConfigurationBasedIssuerNameRegistry' class to resolve the trusted issuer. This class uses the certificate thumbprint to match certificates. Obviously the certificate thumbprint changed when auto certificate rollover issued a new signing certificate. You can avoid this by implementing you own class and for example use the subject name to match issuers.

After adding the thumbprint of the new signing certificate to the trustedIssuers section, the sample should work again ... unless ...

My signing certificate is a self-signed certificate, but the newly created certificate is not trusted on the WCF machine. This results in this error:

ID4257: X.509 certificate 'CN=ADFS Signing - WIN-BEJU5AI4TP7.pbdev.CODit.eu' validation failed by the token handler.

The self-signed signing certificate should be added to the trusted root store of the WCF machine. The action you need to take to make sure the certificate passes validation depends on the certificateValidation mode you use. Mine was set on chaintrust, so adding it to trusted root will do. If you use peertrust, add the certificate to the trusted people store. If you use custom it depends on your implementation.

 

Peter Borremans

Posted in: Security | WCF | WIF

Tags: , , , ,


February 10, 2012 at 9:20 PM

When developing applications that make use of Windows Identity Foundation - WIF (Microsoft's technology to deal with claims-based security in your services) you can encounter exceptions that are not very precise/clear.

In this blog post I will show the difference between WCF tracing and WIF tracing based upon a sample application that is made to fail. The sample application consists of a client calling a service by first obtaining a security token from AD FS 2.0 and then asking the service to enumerate the claims present in the security token. The client will show the claims enumeration as received from the service. 

When I run my application (both the host and the client) this is the result:

 

The window in the background is my ServiceHost, the window in the front is the client. When the client calls the service over transport security the presented claims are enumerated as expected, when doing the same over message security the communication fails.

We will try to find the root cause of the problem by looking into WCF and WIF tracing.

The first thing the seasoned .NET developer will think of is to go in the .config file of your service and enable WCF Tracing:

 

Let's have a look what helpful information we can find in the WCF tracelog and WCF message log after running the sample.

The trace log shows this exception:

 

The message log shows this SOAP fault:

 

Although we have some useful information, "Message security verification failed" and "Security token could not be authenticated" can have a lot of reasons. We are interested in more detailed information about what goes wrong in the communication between clients, AD FS 2.0 and the service.

To find more detailed information we can enable WIF Tracing. WIF tracing cannot be enabled by clicking 'enable' in the Service Configuration Editor tool like we did for WCF Trace. However the idea is similar.

We only need to add two things to our config file (can also be done via the Service Configuration Editor tool):
- A diagnostics source: Microsoft.IdentityModel
- A listener (shared or not)

 The Xml representation looks like this: 

<system.diagnostics>
    <sources>
...
      <source name="Microsoft.IdentityModel" switchValue="Verbose">
         <listeners>
           <add name="WIFListener" />
         </listeners>
      </source>
...
    </sources>
    <sharedListeners>
...
      <add initializeData="c:\...\wif.svclog" type ="System.Diagnostics.XmlWriterTraceListener, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" name="WIFListener" />
    </sharedListeners>
</system.diagnostics>

The Service Configuration Editor tool view looks like this:

 

That't all you need to do to enable WIF tracing.

Let's re-run the sample app with WIF tracing enabled and hit against the same exception...

After opening the WIF trace-file, I can see this information:

 

As you can see, WIF Tracing gives us a much clearer exception message:

"ID1038: The AudienceRestrictionCondition was not valid because the specified Audience is not present in AudienceUris.
Audience: 'http://localhost:8733/ClaimsEnumeratorService/'"

After checking the service config file, I can see that WIF is exaclty right :)

The audienceUri for my endpoint that uses message security is commented out.
Thanks to WIF tracing I can troubleshoot problems like this in a reasonable time, with only WCF tracing the exception details are just not enough to point you to the right direction.

Both WCF and WIF tracing are priceless for .NET developers...

 

Peter Borremans

Posted in: Security | WCF | WIF

Tags: