Understanding Security Features in the SPA Template for VS2013 RC

Understanding Security Features in the SPA Template for VS2013 RC

Rate This
  • Comments 35

The blog is based on Visual Studio 2013 RC release.

We have completely rewritten the SPA template from the previous version( MVC 4). Here are some of the changes we made:

  • The authentication story has completely changed. Instead of using cookie (which was used in the last release), we are using OAuth with a bearer token and OWIN, which is more correct in Web API world.
  • A real single page app. We converted all the app pages into one web page and control the visibility of them by knockout.
  • Account controller are now pure Web API controller
  • Removed the todo samples and made the template generate boilerplate code.
  • Updated to bootstrap to modernize and simplify the CSS story.
  • Updated to the new Identity API.
  • We use attribute routes as default route scenario.

Note that SPA VB template is still not available in this RC release. We will ship it with RTM release.

In this blog, I will focus on the security features in SPA template.

  • Bearer token authentication with Web API.
  • How the bearer token is created:

Bearer Token Authentication with Web API

This is one of the most asked for features in Web API. In the SPA template, we implemented this feature with OWIN security middleware. In order to use OWIN bearer token middleware, you need to make sure this package is installed:

  • Microsoft.Owin.Security.OAuth

Here is the code to enable Bearer Token middleware:

Code Snippet
  1. static Startup()
  2. {
  3.     OAuthOptions = new OAuthAuthorizationServerOptions
  4.     {
  5.         TokenEndpointPath = "/Token",
  6.         AuthorizeEndpointPath = "/api/Account/ExternalLogin",
  7.         Provider = new ApplicationOAuthProvider(PublicClientId, IdentityManagerFactory, CookieOptions)
  8.     };
  9. }
  10.  
  11. // For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
  12. public void ConfigureAuth(IAppBuilder app)
  13. {
  14.     // Enable the application to use bearer tokens to authenticate users
  15.     app.UseOAuthBearerTokens(OAuthOptions, ExternalOAuthAuthenticationType);
  16. }

The UseOAuthBearerTokens extension method actually enables 3 OWIN middleware components:

  1. Authorization Server middleware.
  2. Application bearer token middleware.
  3. External bearer token middleware.

Here is the pseudo-code for the extension method:

Code Snippet
  1. public static void UseOAuthBearerTokens(this IAppBuilder app, OAuthAuthorizationServerOptions options, string externalAuthenticationType)
  2. {
  3.     app.UseOAuthAuthorizationServer(options);
  4.     app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
  5.     {
  6.         AccessTokenFormat = options.AccessTokenFormat,
  7.         AccessTokenProvider = options.AccessTokenProvider,
  8.         AuthenticationMode = options.AuthenticationMode,
  9.         AuthenticationType = options.AuthenticationType,
  10.         Description = options.Description,
  11.         Provider = new AppBuilderExtensions.ApplicationOAuthBearerProvider(),
  12.         SystemClock = options.SystemClock
  13.     });
  14.     app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
  15.     {
  16.         AccessTokenFormat = options.AccessTokenFormat,
  17.         AccessTokenProvider = options.AccessTokenProvider,
  18.         AuthenticationMode = AuthenticationMode.Passive,
  19.         AuthenticationType = externalAuthenticationType,
  20.         Description = options.Description,
  21.         Provider = new AppBuilderExtensions.ExternalOAuthBearerProvider(),
  22.         SystemClock = options.SystemClock
  23.     });
  24. }
   

Authorization server will be responsible to create the bearer token and external bearer token is only used with external logins (Such as Facebook, Google, etc). I will explain them later in the subsequent sections.

After enabling bearer token authentication in active mode, OWIN middleware will authenticate every request with the “Authorization: Bearer” header.  By default, the application bearer token middleware is active.

However, there is still a problem with Web API. What happens if your application enables other authentications?  For example, the SPA template enables application cookie middleware as active mode as well in order to enable other scenarios like MVC authentication. So Web API will still be authenticated if the request has session cookie but without a bearer token. That’s probably not what you want as you would be venerable to CSRF attacks for your APIs. Another negative impact is that if request is unauthorized, both middleware components will apply challenges to it. The cookie middleware will alter the 401 response to a 302 to redirect to the login page. That is also not what you want in a Web API request.

The SPA template demonstrates how to solve this problem in Web API layer. Here is the code snippet from WebApiConfig.Register method:

Code Snippet
  1. // Web API configuration and services
  2. // Configure Web API to use only bearer token authentication.
  3. config.SuppressDefaultHostAuthentication();
  4. config.Filters.Add(new HostAuthenticationFilter(Startup.OAuthOptions.AuthenticationType));

 

SuppressDefaultHostAuthentication will register a message handler and set current principal to anonymous, so no host principal will get passed to Web API. It will also suppress default challenges from OWIN middleware.

HostAuthenticationFilter behavior is the opposite. It will set the principal from specified OWIN authentication middleware. In this case, it is the bearer token middleware, it will also send a challenge to specified middleware when it sees a 401 response. Since this authentication filter is set as global filter, it will apply to all Web APIs. So the result is that Web API will only see the authentication principal from the bearer token middleware and any 401 response from Web API will add a bearer challenge.

Note: the authentication filter is a new filter type in Web API v2. It happens after the message handler but before the AuthorizationAttribute filter. It makes it possible to let you specify different authentication methods at the action level.

After configuring the OWIN bearer token middleware and Web API host authentication settings, you can easily protect your Web API resources with the AuthorizeAttribute.

Your next question is probably how the bearer token is created in the SPA template. In order to conform with OAuth 2.0 spec, we try to use the OWIN Authorization server to create and send bearer tokens to the client in every scenario.  We plan to have a blog post for OWIN Authorization server to explain how it supports all OAuth 2.0 flows. Here I will only explain the authentication flows that are used in SPA template: Resource Owner Password Credentials Grant and Implicit Grant.

Password Login Flow

This flow happens when the user logs in by typing her user name and password in login form ( This is the most common authentication scenario).

In the SPA template, we use OAuth 2.0’s Resource Owner Password Credentials Grant flow for this scenario and implement it in the OWIN Authorization server.

In this flow, the browser sends a POST request with grant type, user name and password and server returns back an access token. For example, the browser sends:

POST http://localhost:47948/Token HTTP/1.1
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Content-Length: 46
Host: localhost:47948

grant_type=password&username=a&password=aaaaaa

The server responds:

HTTP/1.1 200 OK
Content-Length: 671
Content-Type: application/json;charset=UTF-8
Set-Cookie: .AspNet.Cookies=Ud0iQNZazLq-K8C; path=/; HttpOnly

{
"access_token":"YPo047a0sqJUmle6tkeKmIaRUS",
"token_type":"bearer",
"expires_in":1200,
"userName":"a",
".issued":"Thu, 19 Sep 2013 05:56:32 GMT",
".expires":"Thu, 19 Sep 2013 06:16:32 GMT"
}

In order to support the above password login flow, the  SPA template calls ApplicationOAuthProvider and exposes the token endpoint as “/Token”. (See OAuthAuthorizationServerOptions in the Startup.Auth.cs file).

Code Snippet
  1. public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
  2. {
  3.     using (IdentityManager identityManager = _identityManagerFactory.CreateStoreManager())
  4.     {
  5.         if (!await identityManager.Passwords.CheckPasswordAsync(context.UserName, context.Password))
  6.         {
  7.             context.SetError("invalid_grant", "The user name or password is incorrect.");
  8.             return;
  9.         }
  10.  
  11.         string userId = await identityManager.Logins.GetUserIdForLocalLoginAsync(context.UserName);
  12.         IEnumerable<Claim> claims = await GetClaimsAsync(identityManager, userId);
  13.         ClaimsIdentity oAuthIdentity = CreateIdentity(identityManager, claims,
  14.             context.Options.AuthenticationType);
  15.         ClaimsIdentity cookiesIdentity = CreateIdentity(identityManager, claims,
  16.             _cookieOptions.AuthenticationType);
  17.         AuthenticationProperties properties = await CreatePropertiesAsync(identityManager, userId);
  18.         AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
  19.         context.Validated(ticket);
  20.         context.Request.Context.Authentication.SignIn(cookiesIdentity);
  21.     }
  22. }

What the code above does is quite straightforward:

  1. Checks the password with the Identity API .
  2. Create a user identity for the bearer token.
  3. Creates a user identity for the cookie.
  4. Calls the context.Validated(ticket) to tell the OAuthZ server to protect the ticket as an access token and send it out in JSON payload.
  5. Signs the cookie identity so it can send the authentication cookie.

 

External login flow (Microsoft, Facebook, Twitter and Google)

This flow is a little bit complicated compared with password flow.  The SPA template uses OAuth 2.0 implicit flow to convert an external sign in cookie to an access token and send it back to the browser by URL fragment. Here is a simplified flow diagram for a Facebook login:

image

  1. The User clicks the Facebook Login button and it will trigger a browser redirect to the authorization endpoint with a parameter response_type as token and redirect_uri as application URL. For example: http://localhost:47948/api/Account/ExternalLogin?provider=Facebook&response_type=token&client_id=self&redirect_uri=%2F&state=E2VRvHyopw%2BdYR5uKHbHAyYFmK06Pd%2Fw5LRWO243Tdk%3D
  2. The application knows that user wants to login as Facebook so it sends a challenge to the Facebook authentication middleware to modify the response as 302 and redirect browser to facebook.com. To enable an external login service, see Create an ASP.NET MVC 5 App with Facebook and Google OAuth2 and OpenID Sign-on or  External Authentication Service.
  3. The user signs in with her facebook account and which grants access to this application.
  4. Facebook sends a HTTP 302 back and redirects the browser back to Facebook callback URL on server, by default it’s “/signin-facebook”. You can change it with Facebook authentication options.
  5. The Facebook authentication middleware asks for access token and user data from facebook.com.
  6. The Facebook authentication middleware redirects the browser back to the authorization endpoint and converts the facebook data into claims and sets external sign in cookie.
  7. The user agents redirects to the authorization endpoint with the external cookie in the request.
  8. The authorization endpoint checks the external sign in cookie principal and finds the associated application user, then signs in the user as Bearer authentication type into the authorization server middleware. Since the authorization server sees that the request parameter response_type is token (in step 1), it will trigger implicit flow, which will create access the token and append it to the redirect_uri (step 1) as URL fragment. For example:

    HTTP/1.1 302 Found
    Cache-Control: no-cache
    Pragma: no-cache
    Expires: -1
    Location: /#access_token=asd2342SDIUKJdsfjk3234&token_type=bearer&expires_in=1200&state=06hwltIjvnTn44hc
    Set-Cookie: .AspNet.External=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT
    Set-Cookie: .AspNet.Cookies=WJgdyZQs9N8TG20EWnik-j0_; path=/; HttpOnly
    Content-Length: 0

 

The key logic here is in the authorization endpoint, which is “api/Account/ExternalLogin” in the SPA template (See OAuthAuthorizationServerOptions in the Startup.Auth.cs file).

Code Snippet
  1. // GET api/Account/ExternalLogin
  2. [OverrideAuthentication]
  3. [HostAuthentication(Startup.ExternalCookieAuthenticationType)]
  4. [AllowAnonymous]
  5. [HttpGet("ExternalLogin", RouteName = "ExternalLogin")]
  6. public async Task<IHttpActionResult> ExternalLogin(string provider)
  7. {
  8.     if (!User.Identity.IsAuthenticated)
  9.     {
  10.         return new ChallengeResult(provider, this);
  11.     }
  12.  
  13.     ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);
  14.  
  15.     if (externalLogin == null)
  16.     {
  17.         return InternalServerError();
  18.     }
  19.  
  20.     string userId = await IdentityManager.Logins.GetUserIdForLoginAsync(externalLogin.LoginProvider,
  21.         externalLogin.ProviderKey);
  22.  
  23.     bool hasRegistered = userId != null;
  24.  
  25.     if (hasRegistered)
  26.     {
  27.         Authentication.SignOut(Startup.ExternalCookieAuthenticationType);
  28.         IEnumerable<Claim> claims = await ApplicationOAuthProvider.GetClaimsAsync(IdentityManager, userId);
  29.         ClaimsIdentity oAuthIdentity = ApplicationOAuthProvider.CreateIdentity(IdentityManager, claims,
  30.             OAuthOptions.AuthenticationType);
  31.         ClaimsIdentity cookieIdentity = ApplicationOAuthProvider.CreateIdentity(IdentityManager, claims,
  32.             CookieOptions.AuthenticationType);
  33.         AuthenticationProperties properties = await ApplicationOAuthProvider.CreatePropertiesAsync(
  34.             IdentityManager, userId);
  35.         Authentication.SignIn(properties, oAuthIdentity, cookieIdentity);
  36.     }
  37.     else
  38.     {
  39.         IEnumerable<Claim> claims = externalLogin.GetClaims();
  40.         ClaimsIdentity identity = ApplicationOAuthProvider.CreateIdentity(IdentityManager, claims,
  41.             OAuthOptions.AuthenticationType);
  42.         Authentication.SignIn(identity);
  43.     }
  44.  
  45.     return Ok();
  46. }

The OverrideAuthentication attribute is used to suppress global authentication filters. It suppresses the application bearer token host authentication filter in the SPA template.

This action enables ExternalCookieAuthenticationType host authentication, which represents the user’s external sign in state. With this setting, the User.Identity will be set as the external login identity, for example, Facebook identity.

AllowAnonymous enables a user to reach this endpoint without an external sign in state. It will trigger an external sign in challenge when the user is anonymous. That’s the scenario when the unauthorized user clicks the Facebook button to trigger a redirection to facebook.com.

After the browser redirects back from facebook.com and gets the external sign in cookie from the Facebook authentication middleware, this action will check if the external login data has already been associated with existing user.

If it has, it will sign in with both the application bearer token identity and the application cookie identity. As described in the step 8 of external login flow above, it will trigger a redirection and add an access token in URL fragment.

If not, it will sign in with the external bearer token identity and it will also be sent to the client by implicit flow. The client code will check if the user is registered by the code and show up the register external user page as needed. After the user is registered, the client code will trigger the external login flow again to get the application bearer token.

Acknowledgements

I’d like to thank Rick Anderson: (twitter @RickAndMSFT ) for the help on the blog. Rick  is a senior programming writer for Microsoft focusing on Azure and MVC.

 

Thanks,

Hongye Sun (ASP.NET MVC and Web API team)

Leave a Comment
  • Please add 2 and 5 and type the answer here:
  • Post
  • Hi Rick,

    Thanks for sharing this with all readers. I was working with SPA with durandal and was really wondering about security aspects and few more other problems yesterday/today. With your post, I found a ray of hope. :)

    Thanks for all your help.

  • @Lalit, I am glad that you like this blog. I am working on a sample to enable security features on a durandal project. I will share it here once it's done.

  • Thanks for the post, I have just been working through this and found information hard to come by on what all the attributes where doing.

    The part I am struggling with is the Logout.  If I do a POST to the logout via AJAX, I can still call Web API methods with the Bearer token.  My SPA does not require the standard Cookie Authorization to authorize Mvc Controller actions, its completely Anonymous with all authentication applied to the Web API controllers using via the Bearer token.

    Question: How do you terminate a Bearer token? Apart from removing it from the client side.

  • Token revocation is not supported in template. The template depends on expiration for token protection.

    However, you can still support it by your app by storing grant data in database and remove it when revoke. That's exactly what most apps support it today. For example, the grant data can be based on user id and client id.

    GrantRecord {

     int GrantId, // primary key

     UserId, // the user

     ClientId, // the client application

     Scopes, // whatever scopes have already been granted, to automatically re-issue if asked again for example

    }

    In this case the “grant_id” would be an additional claim embedded inside the auth code and flowing to the access_token or refresh_token. You could verify it still exists when the access_token is used. You may customize OAuthBearerAuthenticationProvider.ValidateIdentity to verify it.

  • Hello, I'm trying to figure out how to set the expiration when issuing access tokens from the Single Page Application in VS2013 RC. I noticed your example code above makes use of the OAuthAuthorizationServerOptions class. However I can't seem to find this class in the Owin middleware assemblies. Can you tell me how I can set the access token expiration?

  • @Russell, are you using the RC bits? The class should be in the Microsoft.Owin.Security.OAuth assembly. To change the default expiration time for access token, set it by OAuthAuthorizationServerOptions.AccessTokenExpireTimeSpan.

  • @Hongye, thanks for the reply. I do have VS2013 RC installed (use VS2012 for my primary development). Where can I pull the RC bits? Also, where can I download the SPA template with the Startup() constructor:

    static Startup()

    {

       OAuthOptions = new OAuthAuthorizationServerOptions

       {

           TokenEndpointPath = "/Token",

           AuthorizeEndpointPath = "/api/Account/ExternalLogin",

           Provider = new ApplicationOAuthProvider(PublicClientId, IdentityManagerFactory, CookieOptions)

       };

    }

  • @Hongye, I forgot to mention that I have VS Ultimate 2013 RC installed. Do I need another version for the SPA template that you show above?

  • If you have VS 2013 RC installed, you can create SPA template by New Project / Templates / Visual C# / Web and select ASP.NET Web Application template. In the New ASP.NET Project wizard, select Single Page Application.

  • It seems I didn't have the latest RC bits. After running the following command in the Package Manager Console the OWIN assemblies were updated:

    PM> Install-Package Microsoft.Owin.Security.OAuth -Pre

  • @Hongye, How do you change the AccessTokenExpires depending on a condition?, like remember me scenario.

    I have some persist logic, but it seems to ignore the properties.ExpiresUtc , and takes it from the OAuthOptions set in the Start.Auth.cs.   It does however set the persistent part of the token

    Snippet of my attempt in the AccountController.ExternalLogin

               AuthenticationProperties properties = await ApplicationOAuthProvider.CreatePropertiesAsync(

                   IdentityManager, aspNetUserId);

               if (persist)

               {

                   properties.IsPersistent = true;

                   properties.ExpiresUtc = new DateTimeOffset(DateTime.UtcNow.AddDays(30));

               }

               Authentication.SignIn(properties, oAuthIdentity);

  • @Hongye, I have come up with a solution for setting the AccessToken ExpiresUtc based on certain conditions.

    Does this look Ok? Or is there a easier way to do this?

    First I created my own AccessToken AuthenticationTokenProvider to check for the IsPersistent property, and if exits, then set the ExpiresUtc to another TimeSpan

    public class MyAccessTokenProvider : AuthenticationTokenProvider

       {

           public MyAccessTokenProvider(ISystemClock systemClock, TimeSpan persistentExpiresTimeSpan)

           {

               SystemClock = systemClock;

               PersistentExpiresTimeSpan = persistentExpiresTimeSpan;

           }

           public ISystemClock SystemClock { get; private set; }

           public TimeSpan PersistentExpiresTimeSpan { get; private set; }

           public override void Create(AuthenticationTokenCreateContext context)

           {

               base.Create(context);

               if (context.Ticket.Properties.IsPersistent)

                   context.Ticket.Properties.ExpiresUtc = SystemClock.UtcNow.Add(PersistentExpiresTimeSpan);

           }

       }

    I then configured the OAuthOptions to use MyAccessTokenProvider within the Start.Auth.cs config

    static Startup()

    {

       PublicClientId = "self";

       IdentityManagerFactory = new IdentityManagerFactory(IdentityConfig.Settings,

           () => new IdentityStore(new MyConciergeIdentityDbContext()));

       CookieOptions = new CookieAuthenticationOptions();

       OAuthOptions = new OAuthAuthorizationServerOptions

       {

           TokenEndpointPath = "/Token",

           AuthorizeEndpointPath = "/api/account/externallogin",

           Provider = new ApplicationOAuthProvider(PublicClientId, IdentityManagerFactory, CookieOptions),

           AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(20),

           AccessTokenProvider = new MyAccessTokenProvider(new SystemClock(), TimeSpan.FromDays(30))

       };

    }

    Then I set the IsPersistent property within the AccountController.ExternalLogin method by passing in an extra parameter via the QueryString so it would be called like /api/account/externallogin?provider=Google&p=1.  I noticed that the IsPersistent property is only used by the CookieAuthentication, and not the OAuthBearer code.

    Changed the signature

    public async Task<IHttpActionResult> ExternalLogin(string provider, short? p)

    (partial Snippet from inside ExternalLogin where I added a single line)

    AuthenticationProperties properties = await ApplicationOAuthProvider.CreatePropertiesAsync(

       IdentityManager, aspNetUserId);

    // This is the new line of code I added

    properties.IsPersistent = p.HasValue && p.Value == 1;

    Authentication.SignIn(properties, oAuthIdentity);

  • ExpireUtc property should override the middleware level expire setting. I filed a bug on katanaproject.codeplex.com/.../115.

    Your workaround code is exactly what I will suggest. The AccessTokenProvider.Create is the only place that you can change the ExpireUtc. Thanks.

  • The implementation of the external login in the SPA VS 2013 SPA template changes physical page after the login, and the whole external login procedure is started with a window.location=.....instead that with an ajax call. Is there a way to implement the OAUTH2 protocol without leaving the browser page, with ajax calls only ? Is it possible to handle all client redirects in javascript ? Are cross domain ajax calls the problem that prevent a similar solution?

  • @Francesco, external login provider depends on browser's redirection to prevent user access token from sending to unexpected domain. There is no way that you can avoid the redirection, otherwise it will be security issue. However, there is several ways that you can still keep the user in the same page. One way is to use a pop up window to host the external authorization page and in the callback endpoint using javascript to send data back to window.opener. Or embed an iframe in the your page, and in the callback endpoint use window.parent to pass data back to parent window.

Page 1 of 3 (35 items) 123