Monday, 24 November 2014

Introduction to .NET Web API 2 with C# Part 3: authentication


Introduction
So far in this intro course we haven’t discussed authentication although we enabled it when we created the demo Web Api app. In this demo we’ll see how to make an authenticated request to the API. Let’s imagine that our rockband data is top secret. Therefore not just anyone should gain access to it.
Open the demo app and the simple HTML web app we’ve been working on and let’s get started.
Authentication
Recall from the previous post in this series that we made an anonymous call to the API via the Get Rockbands button from the HTML web app. We’ll see how the need of authentication changes the picture.
Open RockbandsController.cs and place the following attribute over the class declaration:
1
2
[Authorize]
public class RockbandsController : ApiController
Run both apps. Have the Chrome developer tools open in the browser for default.html. Press the Get Rockbands button and you should get a 401 back:
Unauthorised Web API
So we’ll need to authenticate first. However, in order to authenticate we’ll need to sign up with the API app. However, there’s no familiar Signup link as the API page is not the usual MVC page with the login and sign-up links in the top right hand corner. Navigate to /Help on the Web API page to view the available HTTP endpoints. You’ll find a number of actions under the Account category. The POST api/account/register entry looks promising. Click on that link to view what we need to send to the service:
User registration inputs
That doesn’t look terribly difficult. Let’s build a simple UI for that in the HTML web app. Place the following HTML below the h1 tags:
1
2
3
4
5
6
<form id="userSignup">
        <input type="text" name="username" placeholder="Name" />
        <input type="password" name="password" placeholder="Password" />
        <input type="password" name="confirmpassword" placeholder="Confirm password" />
        <input type="submit" id="signup" value="Sign up" />
</form>
Note the “name” attributes of the input tags. They correspond to the properties in the JSON that the Register action expects. The case doesn’t matter: userName, USERNAME, etc. will all do the job.
We’ll now need some javascript to send the data to the service. It’s good that we set up CORS in the previous post so we can call the service from here. We already have some javascript on that page to get the rockband data. Place the following bit of code…
1
2
3
4
5
6
7
8
var register = function () {
                var registrationUrl = "http://localhost:50170/api/Account/Register";
                var registrationData = $("#userSignup").serialize();
                $.post(registrationUrl, registrationData).always(showRockbands);
                return false;
            };
 
$("#signup").click(register);
…above…
1
$("#getRockbands").click(getRockbands);
We call the Register controller with a POST request and send the serialised form data in the message body. We’ll show the response using the showRockbands method we defined earlier. Re-run default.html and try to register without filling in the form:
Validation exception
Not surprisingly we get a validation exception.
Fill in the text boxes and you should get an empty response which means that the request succeeded:
Web API signup OK
So we’re registered but we still cannot access the rockband data. We’ll need to send along an access token. In fact we need to send it along with every request that requires authentication. It’s not the same as a cookie in the case of forms authentication. Instead, we’ll need to send the token in the request header. This is done by logging in with the website using the login credentials and get hold of the access token from the website. If you look at the Help page again in the API app you won’t find any logical candidate for this task. There’s no “login” or “gettoken” endpoint.
The secret is hidden in a completely different place. Locate Startup.Auth.cs in the App_Start folder. This file usesOWIN technology. I’m planning to write a short series on OWIN and KATANA later in case you’re interested in those technologies. The interesting bit of code is the following:
1
2
3
4
5
6
7
8
OAuthOptions = new OAuthAuthorizationServerOptions
{
                TokenEndpointPath = new PathString("/Token"),
                Provider = new ApplicationOAuthProvider(PublicClientId, UserManagerFactory),
                AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
                AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
                AllowInsecureHttp = true
};
The ‘/Token’ bit looks promising. We’ll need to send a POST to /Token with our credentials which will return an access token. You can even test it in a browser, but it won’t succeed of course:
Missing token
This means that we need to specify a grant type which is missing, hence the “unsupported grant type” error message. The endpoint is expecting some data called “grant_type=[type of grant]“. Let’s see how we can build that.
Add the following piece of HTML to the form on the HTML page:
1
2
3
4
5
6
7
<form id="userSignup">
        <input type="text" name="username" placeholder="Name" />
        <input type="password" name="password" placeholder="Password" />
        <input type="password" name="confirmpassword" placeholder="Confirm password" />
        <input type="submit" id="signup" value="Sign up" />
        <input type="submit" id="signin" value="Sign in" />
</form>
Our form will also function as a login form. Not a very practical solution but it will do for demo purposes.
Add the following bit of JavaScript…:
1
2
3
4
5
6
7
8
9
var signin = function () {
                var tokenUrl = "http://localhost:50170/Token";
                var loginData = $("#userSignup").serialize();
                loginData = loginData + "&grant_type=password";
                $.post(tokenUrl, loginData).always(showRockbands);
                return false;
            };
 
$("#signin").click(signin);
…above…
1
$("#signup").click(register);
We’re calling the /Token endpoint and send along a grant type of password. This is telling the endpoint that we need an access token based on the username and password in the form data.
Run both applications and fill in the username and password you signed up with in the previous step. You should see… …the same error as we saw before we enabled CORS. But didn’t we enable CORS already? Yes, but that was in the context of the Web API. We need to do something similar for the /Token endpoint which hides OWIN middleware. Locate ApplicationOAuthProvider.cs in the Providers folder of the Web API project. Search for the following method:
1
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
You’ll see a using block in the method body. Add the following code below the using block:
1
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
Re-run the web api demo app and try to sign in. It should succeed and the token should be visible on the screen:
Bearer token from web api
You’ll find a short course on OAuth here which explains what the parts in this JSON mean if you’re curious. The most important bit of information is the access_token. We’ll need to extract it from the JSON and send it along with every subsequent request to the API. This will allow the request through the Authorize attribute. Modify the JavaScript code…
1
$.post(tokenUrl, loginData).always(showRockbands);
…to the following:
1
2
3
$.post(tokenUrl, loginData)
                    .success(saveAccessToken)
                    .always(showRockbands);
…where saveAccessToken is defined as follows:
1
2
3
4
5
var token = "";
 
var saveAccessToken = function (data) {
      token = data.access_token;
};
We save the access_token property from the JSON that came back from the web api auth request.
Replace…
1
2
3
4
var getRockbands = function () {
         $.get(rockbandSourceUrl).always(showRockbands);
         return false;
};
…with an AJAX request:
1
2
3
4
5
6
7
8
var getRockbands = function () {
                $.ajax(rockbandSourceUrl,
                {
                    type: "GET"
                    , headers: getHeaders()
                }).always(showRockbands);
                return false;
};
…where getHeaders() is defined as follows:
1
2
3
4
5
var getHeaders = function () {
                if (token){
                    return { "Authorization": "Bearer " + token };
                }
};
Re-run the HTML app and login first. Wait for the access token to appear. Then click Get Rockbands and there you are:
Rockbands OK after login
This is slightly more complicated than it should be but it may change in the future. There’s actually a readily available single-page version of we’ve just done among the MVC 5 templates.
If you create a new ASP.NET Web application in Visual Studio you can select the Single Page Application template:
Single page application
It will set up authentication, MVC, Web API, OWIN, jQuery and knockout.js for you for a fancy start-up single page application. Start the application and click on the links. It looks like you’re navigating through controllers but it’s really the same page where knockout.js takes care of showing and hiding different parts on the UI. You can sign up with a username and password and then you’ll see a familiar view but now with the usual welcome message and the Log off link in the top right hand corner:

No comments:

Post a Comment

Angular Tutorial (Update to Angular 7)

As Angular 7 has just been released a few days ago. This tutorial is updated to show you how to create an Angular 7 project and the new fe...