Azure App Services Custom Auth (Part 2: server authentication)
Hello again everyone!
I hope the first part was interesting enough, on this second part we are going to introduce the authentication by token part.
Once again, 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
On the first part we only saw the user management and now we currently have a system that can add users from the client and access them quite simply from the server.
How token-authentication works
We are doing authentication, so it is best if we understand how it works.
I will quickly sum up how token based authentication works.
You can skip this whole part if you are familiar with the concepts and go straight to « Azure App Services Implementation ».
Three-party mechanism
The very first step is to understand that we have three parties involved here.
- A user: well, that makes sense
- A grant provider: this server is in charge of verifying and guaranteeing the user is who he says he is
- An application server: this server trusts the provider that the user is verified by him and uses the informations the provider passes on as trusted
To ensure that trust the grant provider and the application server share a common signing key. In the case of social network providers (Facebook, Twitter, Google, …) these are represented by your app id / app secret.
The two servers will share a token that the user will pass on each request allowing the application server to verify the authentication.
In our case the provider and the application server will be the same server. On an ideal situation we could separate these and avoid stocking the user informations on the application server (such as authentication by social providers).
Signing Key & Token
In case of JWT (JSON Web Tokens) we have a header, a body and a signature. While the header and the body can easily be decrypted (and thus cannot be considered safe) the signature is a hash of the informations with the signing key.
For testing purposes you can decrypt and check signatures of JWT tokens on http://jwt.io/.
That means that only somebody that has the signing key can generate tokens that will be validated by our server. This is a strong symetric signing and a great way to share secured informations.
That also mean that the signing key is the most precious security information. It must never be stored on the client and it must remain hidden and securely stored at anytime by both parties.
/!\ Do not transfer a key by mail, by chat, by SMS, by USB Key, anything, never, ever, ever /!\
also,
/!\ No signing keys in the code, no secrets under version control or stored in a possibly insecure location /!\
A leak of the signing key will compromise your whole security allowing intruders to pretend they are anyone they want to be.
Almost all security hacks are not done by cracking overly complicated security systems.
On this article you will see it would take billions of years to crack even a simple encryption.
Most hacks are done using weak security leaks (password security questions, shared passwords between apps, weak passwords, weak security elsewhere, …) and you must remember that your whole security system is as weak as your weakest point.
Store your secrets and password securely with tools such as KeePass and keep them unique whenever you can.
First authentication by email / password
Back to the good part!
Users authenticate for the first time using their email and their password. This step is necessary to ensure the user is whoever he claims he is.
To do this we are passing the email and password securely (HTTPS, HTTP over SSL) and the provider is checking everything is correct.
In case of success a token is generated. This token encrypts claims such as the nameidentifier, the audience, the issuer, etc.
Token transmission
Each request a user make will include a token in the form of a HTTP Header (X-ZUMO-AUTH
for Azure App Services — aZUre MObile).
The server will check the validity of the token (expiration date, issuer, audience, signature) and use the informations stored in the token locally (user id, user name, …).
Refresh token
A token has an expiration date and to keep a token alive there is a mechanism of token refreshment. I will not cover this here.
Azure App Services Implementation
We are going to use JWT Tokens, OAuth mechanisms from Identity and the existing mechanisms of Azure App Services to keep it as simple as possible.
Strategy
What we need to do to achieve our goal is pretty simple:
- Expose a
/oauth/token
endpoint on which users can send their email / password - Generate claims and an authentication ticket from their informations (or reject him if the infos are wrong)
- Generate and send a token to the user
The existing mechanisms will take care of the rest (validating, interpreting and storing the token). The solution I offer uses consumption mechanisms of Azure App Services.
ApplicationUser.GenerateUserIdentityAsync, generating claims
The first thing we want to do is to generate Claims from a user. For this we will use the existing mechanisms of Identity and go to our ApplicationUser
class to add a method named GenerateUserIdentityAsync
.
The implementation is right here:
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(
UserManager<ApplicationUser> manager,
string authenticationType)
{
var userIdentity = await manager.CreateIdentityAsync(this,
authenticationType);
userIdentity.AddClaim(new Claim(JwtRegisteredClaimNames.Sub,
this.UserName));
return userIdentity;
}
This allows us to generate the ClaimsIdentity from a user which we will use on our authentication mechanism.
We need to add a claim for Azure App Services (which is the Sub claim) on which we will put the username of the user.
CustomAuthProvider, validating the user
We will need a provider that can grant users their credentials. In a folder « Providers » we will create the CustomAuthProvider
, deriving from OAuthAuthorizationServerProvider
. Once again, the existing mechanisms will avoid us to reimplement everything. This class comes from the Microsoft.Owin.Security.OAuth
nuget package.
Here is the implementation of this class:
public class CustomAuthProvider : OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication(
OAuthValidateClientAuthenticationContext context)
{
context.Validated();
return Task.FromResult<object>(null);
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in
context.Properties.Dictionary)
{
context.AdditionalResponseParameters.Add(property.Key,
property.Value);
}
return Task.FromResult<object>(null);
}
public override async Task GrantResourceOwnerCredentials(
OAuthGrantResourceOwnerCredentialsContext context)
{
var allowedOrigin = "*";
context.OwinContext.Response.Headers.Add(
"Access-Control-Allow-Origin",
new[] { allowedOrigin });
var userManager = context.OwinContext
.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName,
context.Password);
if (user == null)
{
context.SetError("invalid_grant",
"The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity =
await user.GenerateUserIdentityAsync(userManager, "JWT");
var props = new AuthenticationProperties(
new Dictionary<string, string> { { "guid", user.Id } });
var ticket = new AuthenticationTicket(oAuthIdentity, props);
context.Validated(ticket);
}
}
On this class we are granting the users credentials from a OAuthGrantResourceOwnerCredentialsContext
generated from the request.
We are fetching the user from the username sent by the context with the user manager and we are creating an AuthenticationTicket
from the JWT claims we generated. Note that I also add the user.Id
in the response, which will be useful later on the client-side.
This AuthenticationTicket
will be encrypted into a token by our next class:
CustomZumoTokenFormat, encrypting claims
In the same folder we will create this class responsible for creating a token from the claims we generated.
The tricky thing here is to fit to the implementation of Azure App Services so we will not have to implement the consumption mechanism again.
public class CustomZumoTokenFormat : ISecureDataFormat<AuthenticationTicket>
{
private string _host = string.Format("https://{0}.azurewebsites.net/",
Environment.ExpandEnvironmentVariables("%WEBSITE_SITE_NAME%")
.ToLower());
public string Protect(AuthenticationTicket data)
{
if (data == null)
throw new ArgumentNullException("data");
// Get Signing Key and send x-zumo-auth token from claims
string signingKey = GetSigningKey();
var tokenInfo = AppServiceLoginHandler.CreateToken(
data.Identity.Claims,
signingKey,
_host,
_host,
TimeSpan.FromHours(24));
return tokenInfo.RawData;
}
public AuthenticationTicket Unprotect(string protectedText)
{
throw new NotImplementedException();
}
private static string GetSigningKey()
{
string key =
Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY");
if (string.IsNullOrWhiteSpace(key))
key = ConfigurationManager.AppSettings["SigningKey"];
return key;
}
}
While on the first versions I had to decompile their code to get the signing key (which had a different name), Azure App Services open sourced their code and included this GetSigningKey() method that I find pretty elegant.
The AppServiceLoginHandler.CreateToken(…)
is also a very helpful class and beautifully exposed. I used to hack my way to get access to this class, decompiling the code to see how they exposed and used it (that was a different name).
What we are doing here is simply taking the AuthenticationTicket
and creating the associated signed token with their handler.
I generated the host with the environment variable because I had trouble getting a correct issuer / audience for this token. If anyone has a better way to get or create the correct issuer I am opened to suggestions in the comments!
OAuthAuthorizationServerOptions, exposing the endpoint
Almost done! What we have left to do is to expose our endpoint and assemble the bricks we created.
For this, just go to your OWIN Startup
class. We will create a new method ConfigureCustomAuth
with this implementation:
public static void ConfigureCustomAuth(IAppBuilder appBuilder)
{
OAuthAuthorizationServerOptions oAuthServerOptions =
new OAuthAuthorizationServerOptions()
{
TokenEndpointPath = new PathString("/oauth/token"),
Provider = new CustomAuthProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
AccessTokenFormat = new CustomZumoTokenFormat(),
};
// OAuth Configuration
appBuilder.UseOAuthAuthorizationServer(oAuthServerOptions);
}
Our endpoint will be exposed on /oauth/token
, with the provider and the token format we created before.
Calling the /oauth/token
endpoint with the user email and password will validate them and create a signed token.
And this is were the magic happens: this token is correctly formatted for Azure App Services which means we do not have to re-implement the consumption!
Finally, we only have to use our method on the Configure
method to expose everything correctly:
public void Configuration(IAppBuilder appBuilder)
{
// … (part 1 code)
ConfigureCustomAuth(appBuilder);
}
Protecting our methods
Now that we have implemented this authentication we can simply use the [Authorize]
keyword on a controller or a method to restrict the acces only to authenticated users.
Here is my implementation of GetUser
to show you:
public class AccountsController : BaseAuthApiController
{
[Route("user/{id:guid}", Name = "GetUserById")]
[Authorize]
public async Task<IHttpActionResult> GetUser(string Id)
{
var userId = User.Identity.GetUserId();
var user = await this.AppUserManager.FindByIdAsync(Id);
if (user != null)
{
if (user.Id != userId)
throw new HttpException(401,
"You cannot get other users informations");
else
return Ok(this.TheModelFactory.Create(user));
}
return NotFound();
}
…
}
This User.Identity.GetUserId()
comes from an extension (IdentityExtensions
from Microsoft.AspNet.Identity.Core
).
Testing the authentication
First do not forget to set locally a signing key (SigningKey
in app settings), this can be any 128 bits key with hexadecimal values, you can find implementations on the internet.
Then we need to add to the code if we want to test locally is to disable the HTTPS, so add this line on the authentication declaration:
OAuthAuthorizationServerOptions oAuthServerOptions =
new OAuthAuthorizationServerOptions()
{
#if DEBUG
AllowInsecureHttp = true, // NEVER IN PRODUCTION!
#endif
TokenEndpointPath = new PathString("/oauth/token"),
Provider = new CustomAuthProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
AccessTokenFormat = new CustomZumoTokenFormat(),
};
Remember we are dealing with passwords which means we absolutely need the HTTPS protocol. If we forget this anyone listening could get the informations of the user.
Now I will assume you have a user created here. You just have to take a REST client such as the awesome chrome extension Advanced REST Client.
Use http://localhost:port/oauth/token
with a POST method.
We will send a form (application/x-www-form-urlencoded
type) with the values
- username : myusername
- password : mypassword
- grant_type : password
The server will answer with a guid
and a access_token
(among other things). The first one is the additional property we passed on, and the second is the encrypted token with your claims.
You can partially decrypt the token at http://jwt.io. You will see the different claims (ver(sion), iss(uer), aud(ience), etc.) and you can check that the signature is right using your signing key.
Now you can test your secure endpoints using this token, simply pass on two headers when you reach them:
- X-ZUMO-AUTH : your_access_token
- ZUMO-API-VERSION : 2.0.0
The first was is responsible of the authentication, the second was introduced in the latest version of Azure App Services to version their back-end.
You can try this for instance on the GetUser method we authorized later.
Finally
To be honest I did not think this post would be this long and that is why I splitted it into a server-side part and a client-side part.
We have set the biggest part of the authentication on this post and I hope it was easy enough to understand. I also hope you are not disappointed by the fact we could not get into the client-side here.
I would like once again to thank Taiseer Joudeh for his amazing posts on Identity, this was really the foundation of all this authentication.
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