In the next few posts, I will be exploring different authentication options for a simple front end/ back end app.  What I would like to demonstrate is the following:

  • Secure access to a server generated web page - in this case an ASP.NET MVC page
  • Secure access to a web api call that will be used by a client side program, in this case AngularJS
  • Demonstrate different levels of security using Security Roles
  • Provide a mechanism for a user to log in
  • To do this I’ve set up a “Model Manager” website that has the following structure

  • / - Home Page
  • /ModelEditor - List Of Models - ASP.NET MVC
    • /Details - View Model Info
    • /Edit - modify a model
    • /Create - add a new model
  • /ModelEditorAngular - List of Models - AngularJS
    • /<model #> - View/Edit/Create individual model
  • /Api - ASP.NET Web API
    • /Models
    • /PartNumbers
  • /Account
    • /Login
  • Basic security is as follows

  • Anyone can hit the home page
  • Authenticated users can view the List of models or view models (both MVC and Angular)
  • Authenticated users in the “ModelEditorRole” can edit models or create new models
  • Other details

  • I’m testing these sites running locally, as well as on a Azure deployed service
  • The application is built on the BikeStore database
  • I’m using a standalone database for user and role security
  • In the first example, I’ve explored the ASP.NET Identity authentication mechanisms, as well as OAuth 2.0.

    Source code is up at github

    ASP.NET Identity Authentication

    ASP.Net Identity is the current out-of-the-box solution for ASP.NET website security.  It is built on Entity Framework, and gives you a lot of flexibility in setting things up.  With the default scaffolding that is part of the standard asp.net project template, it is very easy to provide a login mechanism for your users.   

    To use ASP.NET Identity, you need the following

  • a login interface - provide by the AccountController / Account Views - I didn’t change anything from the default template
  • a SQL database & valid database context.     
  • Authentication configuration in Startup.Auth.cs - not much was changed here.  
  • a mechanism to add and assign roles - I manually assigned roles in the database directly.
  • Setting up the database & context proved to be the most challenging part of this process.  The first step is deciding whether or not you want to use the ASP.NET Identity classes as-is, or to subclass.   If you choose to subclass, you can add custom properties to the User class that will be present when the tables are created.   The convention I’ve seen is to put this customization in an IdentityModels.cs  here’s what mine looks like:

    public class ApplicationUser : IdentityUser
    {
     public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
     {
     // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
     var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
     // Add custom user claims here
     return userIdentity;
     }
    }
    
    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
     public ApplicationDbContext()
     : base("DefaultConnection", throwIfV1Schema: false)
     {
     }
    
     public static ApplicationDbContext Create()
     {
     return new ApplicationDbContext();
     }
    }
    

    The next option is to determine if you want to let the database creation happen automatically, or to enable migrations.   I chose to enable migrations so I could create a Seed() method for setting up some default users & adding the ModelEditorRole.   Frankly I’m not a fan of the implict table creation.  During testing, I ended up creating the security tables inside the BikeStore database by accident.  And while it’s kind of neat that the first time someone logs in the tables get created automatically, why would you ever have a real application deployed this way?  

    ModelManagerSecurityDb

    Once up and running, the logon process is very simple:

    UserLogin

    User_logged_in

    Here’s what a user looks like

    user_record

    OAuth 2.0 Authentication

    The identity system is an effective choice to be sure, but what if you don’t want to manage user passwords, or you want the user to be able to sign in with their google, facebook, etc account?  The answer is OAuth.   OAuth is an open standard for authorization, that is supported by all the major providers.  A website using OAuth can delegate user authentication to the service hosting the user’s account (i.e. Facebook).  the OAuth standard defines how this should work.  Digital Ocean has an excellent overview on Oauth 2.0.

    To use OAuth, you must first choose a provider, and register your application.  You must provide your application name, website, and a redirect URI.   The provider will then provide you with a Client ID and Client Secret, which will be used by your application to authenticate itself with the provider.  

    Once configured, the application follows a special authentication workflow, generally initiated when the user tries to log in to your application.

  • Application redirects the user to the configured Authorization Code Link.  This link contains
    1. The path to the provider’s Authorization endpoint e.g. the Google Sign in Page
    2. The websites client_id
    3. a callback URL for the website - google will redirect back to this URL after authentication
    4. a response_type - the type of “Grant” the application is requesting - which is typically for an “authorization code”
    5. Scope: either a read or write level of access
  • User Logs in to service on the *provider’s* website, not our application.  
  • The provider redirects to the callback URL, sending an authorization code
  • Application requests an Access token.  Must send
    1. authorization code (from previous step)
    2. client id
    3. client secret
  • Access receives an access token in JSON form, which contains
    1. token type, usually “bearer”
    2. expiration
    3. Whether or not this is a refresh token or not
    4. scope
    5. claims about the user e.g. name is Brian, email is Brian@gmail.com, etc.  
  • The application is now authorized.  
  • Your application is not limited to a single provider - you could allow the user to authenticate with Google, Twitter, and Facebook, all at the sametime.  However, you must decide if @brian is the same as Brian@gmail.com and brian@facebook.com.  Of course you could choose to only allow a person to log in with one type of provider as well.  The point being once you have an authorization token, your application can interpret / use that token as you see fit.  

    Out of the box ASP.Net supports Google, Twitter, Facebook, and Microsoft logins as authentication options.  I’m using Google for this example.   On the asp.net side, setting up google authentication is incredibly easy. You simply provide your google API key / secret in /app_start/Startup.auth.cs:

    app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
    {
     ClientId = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
     ClientSecret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    });
    

    Big warning right here.  Do not actually put your API key in here!  This must come from a configuration file, and that file must not be checked into your source code.  Ever.   Exposing your API key means other people can steal it and incur charges against your account.  Hardcoding your API key means that if you need to change your credentials or they are shut off, your app simply stops working.   Scott Hanselman posted a good article on this.  Here I’m more or less following his advice.  Just remember to avoid checking the secret file into any type of source control, and when publishing the file, encrypt it.  

    app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
    {
     ClientId = System.Configuration.ConfigurationManager.AppSettings["GoogleClientId"],
     ClientSecret = System.Configuration.ConfigurationManager.AppSettings["GoogleClientSecret"] 
    });
    

    Publishing to Azure, the Google API Key is in the application settings configuration panel:

     

    GoogleApiKeyStorage

    The bulk of the work involves setting up your google account to allow your website to talk to google.  I’m following the steps outlined in this article.  I will show you the steps I took here, but keep in mind that Google can change their services at any time.  This is the main trade off to using a third party, you simply don’t have control over what they do on their end.  The good news is that 3rd party authentication is really popular, and doesn’t look to be going away any time soon…  

    Step 1: Create a new project

    Step 2: Create credentials

    Step 3: Define Acceptable Javascript origins and Redirect URI’s

  • This should include your local test environment e.g. https://localhost:44300
  • any staging servers you might have
  • your production website address
  • Include HTTP and HTTPS addresses separately.   Ideally you should be using HTTPS only…
  • Step 4: Enable the Google + API

    This is not enabled by default, and authentication will fail if it is not

    Step 5: Get your provided ClientID / Secret and add them to your application

    After configuration, you may attempt to log into the site with your google account.  

  • click on Logon
  • The site redirects you to the google logon.   
  • Chose your google account, and allow google to send your information to the website
  • The site will prompt you to register.   This will create a record in the identity database with your google account.  Note that your password is blank - your website never see’s your account credentials.
  • Fixing Account Redirection

    The default behavior of the identity system is to redirect the user to the logon page whenever they hit a route that requires authorization.  This is very useful for someone who is not logged in, but what happens when someone not in the ModelEditorRole attempts to edit a model?  They get redirected to the logon page again.   This is confusing for the user, since they’ve already logged in.   

    The solution for this is relatively simple, but disappointing.  I can’t imagine a production application that would use the standard redirection as-is, so having to create a custom filter just to fix this issue is irritating.  

    From this Stack overflow answer:

    public class AuthorizeRedirectMVCAttribute : System.Web.Mvc.AuthorizeAttribute
    {
     protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
     {
     base.HandleUnauthorizedRequest(filterContext);
    
     if (filterContext.RequestContext.HttpContext.User.Identity.IsAuthenticated)
     {
     filterContext.Result = new RedirectResult("~/Account/AccessDenied");
     }
     }
    }
    

    Then, change your decorations from [Authorize] to [AuthorizeRedirectMVC].

    Additionally, you need to implement another [Authorize] filter for your API routes, as they do not use the same AuthorizeAttribute class.

    public class AuthorizeRedirectAPIAttribute : System.Web.Http.AuthorizeAttribute
    {
     protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
     {
     base.HandleUnauthorizedRequest(actionContext);
    
     if (actionContext.RequestContext.Principal.Identity.IsAuthenticated)
     {
     actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Forbidden);
     }
     }
    }
    

    Application Security

    With the two authentication mechanisms available, let’s look at how the application works with these.

    First, any time a user tries to access a route that requires a role or authentication, the application will automatically redirect to the logon screen.  

    All WebApi Routes are set with a default Authorize and Require HTTPS Filter.  As mentioned in a previous blog, Service endpoints should never be served over unencrypted HTTP.

    public static class WebApiConfig
    {
    public static void Register(HttpConfiguration config)
    {
     // Web API configuration and services
     config.Filters.Add(new AuthorizeRedirectAPIAttribute());
     config.Filters.Add(new RequireHttpsAttribute());
    

    The ASP.NET MVC Controller for Models use a [Authorize] attribute for the entire controller,  and an Authorize(Roles) for the editing routes  

    namespace ModelManagerOAuthIndividual.Controllers
    {
    [AuthorizeRedirectMVC]
    public class ModelEditorController : Controller
    {
    
    ...
    
    [AuthorizeRedirectMVC(Roles = "ModelEditorRole")]
    public ActionResult Create()
    {
    ...
    

    The Angular portion is served by an MVC Controller that has the authorize attribute.  Within the Angular routes we do not have access to the [Authorize] filters.  To add granular security within the Angular app, I created an API Security Controller:

    public class SecurityController : ApiController
    {
    // /api/security/UserAuthenticated
    [HttpGet]
    [AllowAnonymous]
    public bool UserAuthenticated()
    {
     return RequestContext.Principal.Identity.IsAuthenticated;
    }
    
    // /api/security/UserInrole?rolename=ModelEditorRole
    [HttpGet]
    [AllowAnonymous]
    public bool UserInRole(string roleName)
    {
     return User.IsInRole(roleName);
    }
    }
    

    Inside the Angular controller, we make the security check as the user attempts to access edit mode on the model detail controller

    $scope.enterEditMode = function () {
    
    $http.get("/api/security/UserInRole?roleName=ModelEditorRole").success(
     function (data) {
     if (!data) {
     // note can't use $location.path here, as the MVC Access denied page is "outside" of the 
     // angular app, and $location.path always prepends a leading '/', so you need to use window.location.href
     window.location.href = $location.absUrl().split('ModelEditorAngular')[0] + 'account/accessdenied';
     }
    
     $scope.inEditMode = true;
     }); 
    }
    

    Remember that the actual API calls to access/edit the model data are still secured through our filters.  This is an important point - you need to implement security on both the client and the API calls.  

    Conclusion

    ASP.NET give you a lot of flexible tools for configuring different authentication options.  For a public site, a good strategy would be to include the following security:

  • A selection of 2-3 different OAuth providers - I’d go with Google, Facebook, and either Microsoft or Twitter
  • A way for the users to register a normal account on your site
  • A common security schema that links the different users together
  • It should be noted again that OAuth implementations are not static, and it’s very possible that OAuth is deprecated at some point in the future.  At the very least, you don’t want your site to stop working because google or facebook make a change! The other tradeoff is that if these services have downtime, it will impact your users.  But the number one reason for using OAuth is simply convenience for your users.  It’s more likely that a user will forget their login / password than google or facebook to be down.   I would argue that OAuth is less secure - as it basically allows users to use the same key on many doors.  Implementing OAuth for a financial institution would be a bad idea - that’s basically giving the provider access to that account.   In general, most sites on the Internet would benefit from allowing OAuth.