6
votes

Windows Azure Mobile Services currently doesn't have an option for custom authentication and looking at the feature request

http://feedback.azure.com/forums/216254-mobile-services/suggestions/3313778-custom-user-auth

It isn't coming anytime soon.

With a .NET backend and a .NET application how do you implement custom authentication, so that you don't have to use Facebook, Google or any of their other current providers?

There are plenty of partially completed tutorials on how this this is done with a JS backend and iOS and Android but where are the .NET examples?

2
And of course after I posted this question and answer I found this article: azure.microsoft.com/en-us/documentation/articles/… hiding away. Very similar approach to mine, but better executed. However I will still leave mine here as I believe it offers a simpler explanation and more information on either end on how to get it working. The article by Microsoft has missed a few key points that will leave people confused.Adam
Can you tell me where did you find those partially completed tutorials on jsbackend and iOS for custom authentication? I am not able to find!!user1010819

2 Answers

20
votes

I finally worked through the solution, with some help of the articles listed below, some intellisense and some trial and error.

How WAMS Works

First I wanted to describe what WAMS is in a very simple form as this part confused me for a while until it finally clicked. WAMS is just a collection of pre-existing technologies packaged up for rapid deployment. What you need to know for this scenario is:

enter image description here

As you can see WAMS is really just a container for a WebAPI and other things, which I won't go into detail here. When you create a new Mobile Service in Azure you get to download a project that contains the WebAPI. The example they use is the TodoItem, so you will see code for this scenario through the project.

Below is where you download this example from (I was just doing a Windows Phone 8 app)

enter image description here

I could go on further about this but this tutorial will get you started:

http://azure.microsoft.com/en-us/documentation/articles/mobile-services-dotnet-backend-windows-store-dotnet-get-started/

Setup WAMS Project

You will need your MasterKey and ApplicationKey. You can get them from the Azure Portal, clicking on your Mobile Services App and pressing Manage Keys at the bottom

enter image description here

The project you just downloaded, in the Controllers folder I just created a new controller called AccountController.cs and inside I put

    public HttpResponseMessage GetLogin(String username, String password)
    {
        String masterKey = "[enter your master key here]";
        bool isValidated = true;

        if (isValidated)
            return new HttpResponseMessage() { StatusCode = HttpStatusCode.OK, Content = new StringContent("{ 'UserId' : 'F907F58C-09FE-4F25-A26B-3248CD30F835', 'token' : '" + GetSecurityToken(new TimeSpan(1,0, 0), String.Empty, "F907F58C-09FE-4F25-A26B-3248CD30F835", masterKey)  + "' }") };
        else
            return Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Username and password are incorrect");

    }

    private static string GetSecurityToken(TimeSpan periodBeforeExpires, string aud, string userId, string masterKey)
    {
        var now = DateTime.UtcNow;
        var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
        var payload = new
        {
            exp = (int)now.Add(periodBeforeExpires).Subtract(utc0).TotalSeconds,
            iss = "urn:microsoft:windows-azure:zumo",
            ver = 2,
            aud = "urn:microsoft:windows-azure:zumo",
            uid = userId
        };

        var keyBytes = Encoding.UTF8.GetBytes(masterKey + "JWTSig");
        var segments = new List<string>();

        //kid changed to a string
        var header = new { alg = "HS256", typ = "JWT", kid = "0" };
        byte[] headerBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(header, Formatting.None));
        byte[] payloadBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload, Formatting.None));
        segments.Add(Base64UrlEncode(headerBytes));
        segments.Add(Base64UrlEncode(payloadBytes));
        var stringToSign = string.Join(".", segments.ToArray());
        var bytesToSign = Encoding.UTF8.GetBytes(stringToSign);
        SHA256Managed hash = new SHA256Managed();
        byte[] signingBytes = hash.ComputeHash(keyBytes);
        var sha = new HMACSHA256(signingBytes);
        byte[] signature = sha.ComputeHash(bytesToSign);
        segments.Add(Base64UrlEncode(signature));
        return string.Join(".", segments.ToArray());
    }

    // from JWT spec
    private static string Base64UrlEncode(byte[] input)
    {
        var output = Convert.ToBase64String(input);
        output = output.Split('=')[0]; // Remove any trailing '='s
        output = output.Replace('+', '-'); // 62nd char of encoding
        output = output.Replace('/', '_'); // 63rd char of encoding
        return output;
    }

You can replace what is in GetLogin, with your own validation code. Once validated, it will return a security token (JWT) that is needed.

If you are testing on you localhost, remember to go into your web.config file and fill in the following keys

<add key="MS_MasterKey" value="Overridden by portal settings" />
<add key="MS_ApplicationKey" value="Overridden by portal settings" />

You need to enter in your Master and Application Keys here. They will be overridden when you upload them but they need to be entered if you are running everything locally.

At the top of the TodoItemController add the AuthorizeLevel attribute as shown below

[AuthorizeLevel(AuthorizationLevel.User)]
public class TodoItemController : TableController<TodoItem>

You will need to modify most of the functions in your TodoItemController but here is an example of the Get All function.

    public IQueryable<TodoItem> GetAllTodoItems()
    {
        var currentUser = User as ServiceUser;

        Guid id = new Guid(currentUser.Id);

        return Query().Where(todo => todo.UserId == id);
    }

Just a side note I am using UserId as Guid (uniqueidentifier) and you need to add this to the todo model definition. You can make the UserId as any type you want, e.g. Int32

Windows Phone/Store App

Please note that this is just an example and you should clean the code up in your main application once you have it working.

On your Client App

Install NuGet Package: Windows Azure Mobile Services

Go into App.xaml.cs and add this to the top

    public static MobileServiceClient MobileService = new MobileServiceClient(
          "http://localhost:50527/",
          "[enter application key here]"
    );

In the MainPage.xaml.cs I created

public class Token
{
    public Guid UserId { get; set; }
    public String token { get; set; }
}

In the main class add an Authenticate function

    private bool Authenticate(String username, String password)
    {
        HttpClient client = new HttpClient();
        // Enter your own localhost settings here
        client.BaseAddress = new Uri("http://localhost:50527/");

        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        HttpResponseMessage response = client.GetAsync(String.Format("api/Account/Login?username={0}&password={1}", username, password)).Result;

        if (response.StatusCode == System.Net.HttpStatusCode.OK)
        {
            var token = Newtonsoft.Json.JsonConvert.DeserializeObject<Token>(response.Content.ReadAsStringAsync().Result);

            App.MobileService.CurrentUser = new MobileServiceUser(token.UserId.ToString());
            App.MobileService.CurrentUser.MobileServiceAuthenticationToken = token.token;

            return true;
        }
        else
        {
            //Something has gone wrong, handle it here
            return false;
        }           

    }

Then in the Main_Loaded function

    private void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        Authenticate("test", "test");

        RefreshTodoItems();
    }

If you have break points in the WebAPI, you will see it come in, get the token, then come back to the ToDoItemController and the currentUser will be filled with the UserId and token.

You will need to create your own login page as with this method you can't use the automatically created one with the other identity providers. However I much prefer creating my own login screen anyway.

Any other questions let me know in the comments and I will help if I can.

Security Note

Remember to use SSL.

References

[] http://www.thejoyofcode.com/Exploring_custom_identity_in_Mobile_Services_Day_12_.aspx

[] http://www.contentmaster.com/azure/creating-a-jwt-token-to-access-windows-azure-mobile-services/

[] http://chrisrisner.com/Custom-Authentication-with-Azure-Mobile-Services-and-LensRocket

1
votes

This is exactly how you do it. This man needs 10 stars and a 5 crates of beer!

One thing, I used the mobile Service LoginResult for login like: var token = Newtonsoft.Json.JsonConvert.DeserializeObject(response.Content.ReadAsStringAsync().Result);

Hope to get this into Android now!