3

I am trying to create a demo project which uses .Net ASP.Net WebAPI and KnockoutJs as the front end. I have created the controller methods that listen for the /token post, and validates a user, and returns a token. This is done from an Ajax Post from the Knockout View Model.

This code works. However, when I get 200 back (Success) from the webApi, I then redirect to a controller method, decorated with a [Authorize]. And that's where I hit a 401 - not authorised.

Login()
    {
        var data = {
            username : this.login.emailAddress(),
            password : this.login.password(),
            RememberMe: this.login.rememberMe(),
            grant_type: "password"
        }

        return $.ajax({
            type: "POST",
            data: data,
            dataType: "json",
            url: "/token",
            contentType: "application/json"
        }).done((reply) => {
            window.location.href = "/Home/AnotherThing";
        });

    }

I think the issue is - I get a response back from my /token (login) call, but do nothing with it. I'm not sure what to do with the token. I stupidly thought that OAuth would somehow put the token into my headers, and they would be there magically. I was wrong.

So, I've been looking for an example, and then best I can find is Here

But this means I am going to have a LOT of repeated code, on each view model

Extract:

function ViewModel() {  
    var self = this;  

    var tokenKey = 'accessToken';  
    var RefTokenKey = 'refreshToken';  
    self.result = ko.observable();  
    self.user = ko.observable();  

    self.token = ko.observable();  
    self.refreshToken = ko.observable();  

    function showError(jqXHR) {  
        self.result(jqXHR.status + ': ' + jqXHR.statusText);  
    }  

    self.callApi = function () {  

        self.result('');  

        var token = sessionStorage.getItem(tokenKey);  

        var headers = {};  
        if (token) {  
            headers.Authorization = 'Bearer ' + token;  
        }  

        $.ajax({  
            type: 'GET',  
            url: '/api/values',  
            headers: headers  
        }).done(function (data) {  
            self.result(data);  
        }).fail(showError);  
    }  

    self.callToken = function () {  
        self.result('');  
        var loginData = {  
            grant_type: 'password',  
            username: self.loginEmail(),  
            password: self.loginPassword()  
        };  

        $.ajax({  
            type: 'POST',  
            url: '/Token',  
            data: loginData  
        }).done(function (data) {  
            self.user(data.userName);  
            // Cache the access token in session storage.  
            sessionStorage.setItem(tokenKey, data.access_token);  
            var tkn = sessionStorage.getItem(tokenKey);  
            $("#tknKey").val(tkn);  
        }).fail(showError);  
    }  

}  

var app = new ViewModel();  
ko.applyBindings(app);

This seems to be part of what I am missing:

sessionStorage.setItem(tokenKey, data.access_token);  
                var tkn = sessionStorage.getItem(tokenKey);  
                $("#tknKey").val(tkn);  

Would I need every view model to have the code that then goes to the sessionStorage, and get the token?

So, this:

var token = sessionStorage.getItem(tokenKey);  

        var headers = {};  
        if (token) {  
            headers.Authorization = 'Bearer ' + token;  
        }  

        $.ajax({  
            type: 'GET',  
            url: '/api/values',  
            headers: headers  
        }).done(function (data) {  
            self.result(data);  
        }).fail(showError);  
    } 

It seems like a lot of code.. Is this the right way to go?

Craig
  • 18,074
  • 38
  • 147
  • 248

1 Answers1

3

Ok, so what you could do is attach the bearer token to each of your HTTP requests. I assume you're using jQuery there? If that's the case you could leverage the beforeSend config param.

Extract a reusable method such as this:

function onBeforeSend(xhr, settings) {    
  var token = sessionStorage.getItem(tokenKey);  

  if (token) {  
    xhr.setRequestHeader('Authorization', 'Bearer ' + token ); 
  }  
}

And then simply attach that method to each of your $.ajax calls that require the token, like this:

$.ajax({  
  type: 'GET',  
  url: '/api/values',  
  headers: headers,
  beforeSend: onBeforeSend
}).done(function (data) {  
  self.result(data);  
}).fail(showError);  

The onBeforeSend function obviously needs to be accessible by your ajax call (I'm not a knockout guy so I don't know if it has any constructs such as services, but if not, you could namespace it for example to avoid making it a global function, but your code organization is up to you).

This way you'll only have to add the beforeSend: onBeforeSend bit to each request that requires auth and it will avoid unnecessary code duplication.

Community
  • 1
  • 1
tkit
  • 8,082
  • 6
  • 40
  • 71
  • Thanks @pootzko - I'm not suing requireJs or anything like that, so I guess I'll need to, and then have a 'shared' scripts file, include that, and we should be good to go. I'll try this out this evening. THanks! – Craig Mar 23 '17 at 04:14
  • I've bombed even before getting to your solution. In the example I posted, my IDE knows nothing about "headers.Authorization = 'Bearer ' + token; ". It's unaware (as am I) of what 'Authorization' is. – Craig Mar 24 '17 at 00:44
  • @Craig yeah but, it's just a JS object.. and an anonymous one at that. It's JS, it's dynamic and you can do whatever you want. And wiith my example you don't need that line anyway because it uses `xhr.setRequestHeader()`. I'd say try if it works first, and then bother about the IDE complaining. – tkit Mar 24 '17 at 11:15