Azure App Services Custom Auth (Part 1: user management)
Greatings everyone!
I have spent a lot of time making a custom auth work on Azure App Services with a .NET backend. And it is about time I share this, as many users need it.
I first published a working custom auth around April 2015, on a version of App Services that was clearly not designed to do this simply.
I had to hack my way through a lot of complicated stuff at that time and now that App Services have been improved and simplified (and is getting ready for production), I feel it is time to make a huge post about custom auth.
I have spent so much time trying to figure this out that I truly believe this blog post might help a lot of users.
This post is part of a whole:
- Azure App Services Custom Auth (Part 1: user management)
- Azure App Services Custom Auth (Part 2: server authentication)
- Azure App Services Custom Auth (Part 3: client authentication)
- Azure App Services Custom Auth (Part 4: cross-provider users) — soon
What you need to know
You need basic knowledge on these stuff, not expertise. But I will not be covering them, first because many people did this way better than me and second because I want to focus on other things.
- ASP.Net:
Needless to precise, but still - Code first migrations: You will need to know how to do it, and to have done it for your business context
- OWIN: We are going to mess up with it.
- Azure App Services: I will not cover up how to set-up AAS
You also need to know that I use another tutorial as a base, made by a far more advanced developper than I am: Taiseer Joudeh.
His tutorials on AspNet.Identity were the real reason I could achieve this custom auth. Let's take a minute of silence to honor his talent.
…
Now, you can find the part 1 of his tutorials here: http://bitoftech.net/2015/01/21/asp-net-identity-2-with-asp-net-web-api-2-accounts-management/
If you look at them, you will see a lot of code is shared for this particular post, and I truly hope he does not think I am just copying the content as this is for me the base for what is coming next. Adapting his work on Azure App Services is the true value of my blog posts. Think of it as public class PaulBlogPost : TaiseerBlogPost
.
Basically, we are going to use AspNet.Identity. Note that you do not necessarly need to use it, but I feel this is the most simple way to achieve our goal.
ApplicationUser
This class will represent a user for us, and will inherit IdentityUser
. You will need the nuget package Microsoft.AspNet.Identity.EntityFramework
in order to do this.
Here is a basic implementation of ApplicationUser:
public class ApplicationUser : IdentityUser
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public DateTime JoinDate { get; set; }
}
Note that these three properties are custom-made, you could very well not use them, or defining other properties.
For instance I have a property ProfilePictureUri
for the picture of the user. I also have a Provider
property, to know if my user comes from Facebook, my auth, or anything else.
What you put on a user to define him is really up to you!
ApplicationDbContext
You probably already have a Context for you business logic. We are going to create a whole new context for the users.
Even if you do not necessarly need to do this, I think it is the best way to go because you need to decouple as much as possible your user private informations from their application data.
That way if one day you want to create your own authentication server you have no link between the user data and the user informations.
Now this is what this context look like:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
private const string connectionStringName = "Name=MS_TableConnectionString";
public ApplicationDbContext() : base(connectionStringName, throwIfV1Schema: false)
{
Configuration.ProxyCreationEnabled = false;
Configuration.LazyLoadingEnabled = false;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.HasDefaultSchema("Application");
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
I use the MS_TableConnectionString
that is defined on App Services to use the same database.
Not that I put the default schema Application
to decouple at a schema level the User informations from their data.
ApplicationConfiguration & Initial Migration
As we have two schema / context, it makes sense to create two configurations. You should already have a configuration for your business logic, we will now create another one for the other Context:
internal sealed class ApplicationConfiguration : DbMigrationsConfiguration<ApplicationDbContext>
{
public ApplicationConfiguration()
{
AutomaticMigrationsEnabled = false;
}
}
Really nothing special here. Regarding your architecture, I recommend to have a folder « Migrations », on which you will have two folders: « Application » and « Business » (or the name of your app instead of Business).
Now create your initial migration with the following line:
Add-Migration InitialApplicationMigration -ConfigurationTypeName ApplicationConfiguration
ApplicationUserManager
This manager will allow us to get the users, and will be used by OWIN. The UserManager class is in the nuget package Microsoft.AspNet.Identity.Core
.
It needs basic stuff, such as a factory, and here is the implementation:
public class ApplicationUserManager : UserManager<ApplicationUser>
{
public ApplicationUserManager(IUserStore<ApplicationUser> store) : base(store)
{
UserValidator = new UserValidator<ApplicationUser>(this) { AllowOnlyAlphanumericUserNames = false };
}
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options,
IOwinContext context)
{
var appDbContext = context.Get<ApplicationDbContext>();
var appUserManager = new ApplicationUserManager(new UserStore<ApplicationUser>(appDbContext));
return appUserManager;
}
}
Just to clarify, I used AllowOnlyAlphanumericUserNames
to use the email address as the username. You can do as you want on your side.
OwinStartup, OwinContext & DbMigrator
Let's get down to business with OWIN. We have two things to do, define the ApplicationUserManager per OWIN Context and update database on start.
I advise you to update your database on start, because that way you will not need to bother to update your databases on your servers each time. The simple fact that you push in production will set the database up to date regarding your current schema and migrations.
Now we have to create the OWIN Startup class: right click on your project -> Add -> New Item -> OWIN Startup class.
You will end up with an empty class with a Configuration
method. We will first add the factory for the context and the user manager:
public void Configuration(IAppBuilder appBuilder)
{
appBuilder.CreatePerOwinContext(ApplicationDbContext.Create);
appBuilder.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// …
}
This will allow us to access the manager and the context from the middleware. This is part of the Microsoft.AspNet.Identity.Owin
nuget package.
Then we add our basic mobile code:
public void Configuration(IAppBuilder appBuilder)
{
// …
HttpConfiguration config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
new MobileAppConfiguration()
.AddMobileAppHomeController()
.AddTablesWithEntityFramework()
.MapApiControllers()
.ApplyTo(config);
appBuilder.UseWebApi(config);
// …
}
Note that you may have a different mobile configuration, use what you need.
And finally we add our database migration code:
public void Configuration(IAppBuilder appBuilder)
{
// …
ConfigureDbMigration();
}
The method ConfigureDbMigration is where we configure our migrations and here is the implementation:
private void ConfigureDbMigration()
{
// DbMigrator businessMigrator = new DbMigrator(new BusinessConfiguration());
// businessMigrator.Update();
DbMigrator applicationMigrator = new DbMigrator(new ApplicationConfiguration());
applicationMigrator.Update();
}
I have commented the business part, as what I want to show you here is the application migration. But as you can see, I have a migration for both my Configurations.
Accounts Api Controller
Now that we have defined all we need to manipulate users, create them and stuff. But we still need to expose this through a controller.
Here is the definition of my controller:
[RoutePrefix("api/accounts")]
public class AccountsController : BaseAuthApiController
{
[Route("user/{id:guid}", Name = "GetUserById")]
[Authorize]
public async Task<IHttpActionResult> GetUser(string Id);
[Route("user/{username}", Name = "GetUserByName")]
[Authorize]
public async Task<IHttpActionResult> GetUserByName(string username);
[Route("create")]
[AllowAnonymous]
[ResponseType(typeof(UserReturnModel))]
public async Task<IHttpActionResult> CreateUser(AccountBindingModel createUserModel);
}
Once again, Taiseer did this work, so I will let you find the implementation of this controller on his amazing blog post from step 7 to 10: http://bitoftech.net/2015/01/21/asp-net-identity-2-with-asp-net-web-api-2-accounts-management/
Just note the use of [Authorize] and [AllowAnonymous], which will ensure the methods are used correctly.
Call controller from client
You cannot use the [Authorize] controlled methods yet because we did not grant users right, at least for the moment.
But I think we can already introduce the use of the Api from the front-end. I will be doing this with the .NET SDK (I am on Xamarin), but any other SDK would work basically the same way.
As you have seen, the CreateUser method uses two models: AccountBindingModel
and UserReturnModel
. We will need them both on the client-side. UserReturnModel
is really close to ApplicationUser and that is the name I used on the client (it was more explicit):
public class AccountModelBinding
{
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Password { get; set; }
public string ConfirmPassword { get; set; }
}
public class ApplicationUser
{
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
Now with these elements, the beauty of the SDK does it all for us, by using .InvokeApiAsync
:
IMobileServiceClient mobileServiceClient = new MobileServiceClient("http://mywebsite.azurewebsites.net/");
AccountModelBinding accountModelBinding = …; // fill with your informations
ApplicationUser createdUser = await mobileServiceClient.InvokeApiAsync<AccountModelBinding, ApplicationUser>("accounts/create", accountModelBinding);
Error handling from the client
If you want to handle the errors, you can simply catch the MobileServiceInvalidOperationException
and look at the content like this:
try
{
// …
}
catch (MobileServiceInvalidOperationException exception)
{
if (exception.Response.StatusCode == HttpStatusCode.BadRequest)
{
/* If you don't have the latest framework */
Task<string> readContentTask = exception.Response.Content.ReadAsStringAsync();
readContentTask.Wait();
if (readContentTask.Result.Contains("is already taken."))
throw new AccountAlreadyTakenException("Account already taken", exception);
else
throw;
}
else
throw;
}
Or even better with the latest version of .NET where you can await on catch:
try
{
// …
}
catch (MobileServiceInvalidOperationException exception)
{
if (exception.Response.StatusCode == HttpStatusCode.BadRequest)
{
/* If you have the latest framework */
string content = await exception.Response.Content.ReadAsStringAsync();
if (content.Contains("is already taken."))
throw new AccountAlreadyTakenException("Account already taken", exception);
else
throw;
}
else
throw;
}
AccountAlreadyTakenException being an exception that I created for this occasion. Check out the HttpStatusCode you need to handle and the message writen with it to filter the exceptions.
I recommend using custom exceptions to handle this like I did.
Finally
You have now made the first step for a custom authentication on Azure App Services, by handling user management.
On the next episode, we will see how to grant users the access to the precious [Authorize] methods (baised on claims).
If you have any question or any remark to make please feel free use the comments below, I will be glad to know what you think of all this if some points are unclear or if you have suggestions.
Paul, out!
This post is part of a whole:
- Azure App Services Custom Auth (Part 1: user management)
- Azure App Services Custom Auth (Part 2: server authentication)
- Azure App Services Custom Auth (Part 3: client authentication)
- Azure App Services Custom Auth (Part 4: cross-provider users) — soon