Author avatar

Peter Mbanugo

Implementing Simple Oauth Server with Katana and MembershipReboot

Peter Mbanugo

  • Jan 10, 2019
  • 11 Min read
  • 34,101 Views
  • Jan 10, 2019
  • 11 Min read
  • 34,101 Views
Microsoft.NET

Introduction

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 Nuget packages

  • First install SimpleInjector, a dependency injection container

Install-Package SimpleInjector.Integration.WebApi

  • Then install the packages for MembershipReboot.

Install-Package BrockAllen.MembershipReboot.Owin

Install-Package BrockAllen.MembershipReboot.Ef

Configuration

Configure the MembershipReboot and DI container and register the container to be used to resolve dependencies at runtime

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" />

Configure MembershipReboot in code

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}
csharp

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.

Configure SimpleInjector

After this, configure SimpleInjector to resolve MembershipReboot's classes at runtime. Add a new class in the App_Startfolder 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}
csharp

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}

OAuth2 token endpoint setup

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 OAuthAuthorizationServerProviderand 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}
csharp

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}
csharp

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}
csharp

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}
json

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.