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.

1.1. Creating the new 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

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

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

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.

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.

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.
1c5ab84f-4cac-4a7f-95a3-e77aefd504ae|7|4.7