In my previous post about .NET, I covered securing and managing user accounts with MembershipReboot in ASP.NET MVC. In this post, I'll walk you through implementing the Resource Owner Password Credential Grant type in the ASP.NET web API.
If you're reading this, I'll assume that you have a fair amount of knowledge about the Owin (Open Web Interface) middleware in ASP.NET and that you understand OAuth2 and Katana well. Take a look here if you need a refresher on any of the above.
I won't cover these topics, but I'll provide code which you can add to your ASP.NET project. That way, you will be able to use this grant type for securing access to your service specifically.
The Resource Owner Password Credential Grant type is suitable in cases where the resource owner (user's of the application who own a specific resource) has a trust relationship with the client of the web service we want to protect (the application that requests access to a resource on behalf of the resource owner). For more on the importance of grant types, check out the OAuth2 page.
To get started, let's install the following nuget packages.
Install-Package SimpleInjector.Integration.WebApi
Install-Package BrockAllen.MembershipReboot.Owin
Install-Package BrockAllen.MembershipReboot.Ef
Next up is to configure the frameworks we added previously. I'll start by adding a connection string to the database that stores the user credentials in the web.config
file.
1<add name="MembershipReboot" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=MembershipRebootWebAPI;Integrated Security=True" providerName="System.Data.SqlClient" />
Let's configure MembershipReboot. I usually prefer doing this in code, and having it in a file in the App_Start
folder.
Add a file named MembershipRebootConfig
in the App_Start
folder, with the following content:
1using BrockAllen.MembershipReboot;
2
3public class MembershipRebootConfig
4{
5 public static MembershipRebootConfiguration Create()
6 {
7 var config = new MembershipRebootConfiguration
8 {
9 MultiTenant = true,
10 RequireAccountVerification = true,
11 EmailIsUsername = true,
12 AllowLoginAfterAccountCreation = true
13 };
14
15 return config;
16 }
17}
Above, I enabled multiple tenant because the user accounts will be stored on one tenant, and the API client credentials will be on another. Other settings in the file can be modified as you want. This file added is optional as you can add this same settings in the web.config file.
After this, configure SimpleInjector to resolve MembershipReboot's classes at runtime. Add a new class in the App_Start
folder called, with the following content in it
1public static class SimpleInjectorWebApiInitializer
2{
3 /// <summary>Initialize the container and register it as Web API Dependency Resolver.</summary>
4 public static void Initialize(IAppBuilder app)
5 {
6 var container = new Container();
7 container.Options.DefaultScopedLifestyle = new ExecutionContextScopeLifestyle();
8
9 InitializeContainer(container);
10
11 container.RegisterWebApiControllers(GlobalConfiguration.Configuration);
12
13 container.Verify();
14
15 GlobalConfiguration.Configuration.DependencyResolver =
16 new SimpleInjectorWebApiDependencyResolver(container);
17
18 //allow scoped instances to be resolved during an OWIN request
19 app.Use(async (context, next) =>
20 {
21 using (container.BeginExecutionContextScope())
22 {
23 context.Environment.SetUserAccountService(() => container.GetInstance<UserAccountService>());
24 await next();
25 }
26 });
27 }
28
29 private static void InitializeContainer(Container container)
30 {
31 System.Data.Entity.Database.SetInitializer(new System.Data.Entity.MigrateDatabaseToLatestVersion<DefaultMembershipRebootDatabase, BrockAllen.MembershipReboot.Ef.Migrations.Configuration>());
32
33 container.RegisterSingleton<MembershipRebootConfiguration>(MembershipRebootConfig.Create);
34 container.Register<DefaultMembershipRebootDatabase>(() => new DefaultMembershipRebootDatabase(), Lifestyle.Scoped);
35
36 var defaultAccountRepositoryRegistration =
37 Lifestyle.Scoped.CreateRegistration<DefaultUserAccountRepository>(container);
38
39 container.AddRegistration(typeof(IUserAccountQuery), defaultAccountRepositoryRegistration);
40 container.AddRegistration(typeof(IUserAccountRepository), defaultAccountRepositoryRegistration);
41 container.Register<UserAccountService>(() => new UserAccountService(container.GetInstance<MembershipRebootConfiguration>(), container.GetInstance<IUserAccountRepository>()), Lifestyle.Scoped);
42 }
43}
Above, I've registered MembershipReboot's dependencies, and also added code to make SimpleInjector resolve dependies during an Owin request.
Open the Owin startup class and call Initialize
:
1//Startup.cs
2public void Configuration(IAppBuilder app)
3{
4 SimpleInjectorWebApiInitializer.Initialize(app);
5}
Next we need to set up the OAuth 2.0 token endpoint to support the Resource Owner Password Credentials Grant type by using the OAuthAuthorizationServerMiddleware
which comes with the Microsoft.Owin.Security.OAuth
library. This requires an authorization server which will then be wired up to the Katana pipeline.
Writing an authorization server using Katana revolves around a class that derives from OAuthAuthorizationServerProvider
, which contains methods that let us manipulate the OAuth2 protocol.
To do this, I'll add a class called MyOAuthAuthorizationServerProvider
which derives from OAuthAuthorizationServerProvider
and override methods ValidateClientAuthentication and GrantResourceOwnerCredentials.
1public class MyOAuthAuthorizationServerProvider : OAuthAuthorizationServerProvider
2{
3 public override System.Threading.Tasks.Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
4 {
5 string cid, csecret;
6 if (context.TryGetBasicCredentials(out cid, out csecret))
7 {
8 var svc = context.OwinContext.Environment.GetUserAccountService<UserAccount>();
9 if (svc.Authenticate("clients", cid, csecret))
10 {
11 context.Validated();
12 }
13 }
14 return Task.FromResult<object>(null);
15 }
16
17 public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
18 {
19 var svc = context.OwinContext.Environment.GetUserAccountService<UserAccount>();
20 UserAccount user;
21 if (svc.Authenticate("users", context.UserName, context.Password, out user))
22 {
23 var claims = user.GetAllClaims();
24
25 var id = new System.Security.Claims.ClaimsIdentity(claims, "MembershipReboot");
26 context.Validated(id);
27 }
28
29 return base.GrantResourceOwnerCredentials(context);
30 }
31}
In the ValidateClientAuthentication method, I have code that validates the client credentials and calls OAuthValidateClientAuthenticationContext.Validated()
method if the credentials matches a record in the database. If the client is validated, then it moves on to validate the user or resource owner credentials (username and password), and calls the Validated() method on the instance of OAuthGrantResourceOwnerCredentialsContext
that will be passed in to the method GrantResourceOwnerCredentials.
Now we need to wire up the authorization server middleware to the Owin pipeline. There is a shorthand extension method on IAppBuilder to use this middleware, which is UseOAuthAuthorizationServer. We will use this extension method to configure the OAuth 2.0 endpoints in the Startup class:
1public void Configuration(IAppBuilder app)
2{
3 SimpleInjectorWebApiInitializer.Initialize(app);
4
5 app.UseOAuthAuthorizationServer(new Microsoft.Owin.Security.OAuth.OAuthAuthorizationServerOptions
6 {
7 AllowInsecureHttp = true,//should be disabled in production
8 Provider = new MyOAuthAuthorizationServerProvider(),
9 TokenEndpointPath = new PathString("/token")
10 });
11}
Also, we're going to add the bearer token authentication middleware for consuming the token:
1public void Configuration(IAppBuilder app)
2{
3 SimpleInjectorWebApiInitializer.Initialize(app);
4
5 app.UseOAuthAuthorizationServer(new Microsoft.Owin.Security.OAuth.OAuthAuthorizationServerOptions
6 {
7 AllowInsecureHttp = true,
8 Provider = new MyOAuthAuthorizationServerProvider(),
9 TokenEndpointPath = new PathString("/token")
10 });
11
12 // token consumption
13 var oauthConfig = new Microsoft.Owin.Security.OAuth.OAuthBearerAuthenticationOptions
14 {
15 AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
16 AuthenticationType = "Bearer"
17 };
18 // Enable the application to use bearer tokens to authenticate users
19 app.UseOAuthBearerAuthentication(oauthConfig);
20}
To get a token, we need to make a post request to localhost/token passing in the necessary parameters. For example:
1POST http://localhost:19923/token
2Content-Type: Application/x-www-form-urlencoded
3
4username=jbloggs&password=pass1234&grant_type=password
At this point, we'll obtain a result like the following:
1{
2 "access_token":
3 "XoHocsL0wOJBVVrjvSj6GpdGD-VrPD2ainZGyeZ8ji0aTq33epyHw72POhB8evYn41fzaSnjx7eo0iclADQBWTMgnghgZdXzNoLo6hwf4Y3SiB0aPTPgZi6PJwoGQK_aMWW62770jo6PBznPrSO0AOZUIrpxjUSZze90-HJjsM9ZgATIWdIvuiICqVjW7n5Z-o0GNSoDTIm-4k2zee0-c_lifHuLmW97IbsQ3I4gMz1SCBReSJtXJc8noPHvgwhFB_qZ2R1-TxR64nUVgxYYtBAoy9n6WKTgNAqrnUwsa0jfMk6wselrLwMGq-R-6_AX4bkh16OZBTGa5hXVWoLPIHl2JTKCkO2DsX2jqvp3J7PObRkZWMyUOyzwhnQWu_XTpn4ogwtcJvLulfiA6W01s8qiUQO--Xefm38ngu5HTM4",
4 "token_type": "bearer",
5 "expires_in": 1199
6}
And that's all we need to get rolling. As you can see, the token has a type and an expiration date. Clean and secure! I've got a sample project on here on GitHub.