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:
To do this I’ve set up a “Model Manager” website that has the following structure
- /Details - View Model Info
- /Edit - modify a model
- /Create - add a new model
- /<model #> - View/Edit/Create individual model
- /Models
- /PartNumbers
- /Login
Basic security is as follows
Other details
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
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?
Once up and running, the logon process is very simple:
Here’s what a user looks like
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.
- The path to the provider’s Authorization endpoint e.g. the Google Sign in Page
- The websites client_id
- a callback URL for the website - google will redirect back to this URL after authentication
- a response_type - the type of “Grant” the application is requesting - which is typically for an “authorization code”
- Scope: either a read or write level of access
- authorization code (from previous step)
- client id
- client secret
- token type, usually “bearer”
- expiration
- Whether or not this is a refresh token or not
- scope
- claims about the user e.g. name is Brian, email is Brian@gmail.com, etc.
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:
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
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.
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:
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.