2
votes

I'm trying to design my first public API, and I'm trying to learn how REST works with authentication, especially in the context of completely client-side apps using js-frameworks, e.g., angularJS.

Say you have a client which is a browser application (i.e., HTML, JS, CSS only) served as static files from something like nginx using a javascript framework to consume a REST service from, e.g. something that requires a secret access key that's used to create a signature for each request to the service, something like Amazon S3.

In terms of authentication in this scenario, where you don't have a server-side application, how would the secret access key be handled, i.e., how do you get it, where do you store it, etc.? It would seem like a horrible security situation to serve the key for each request (even if it only happens once to bootstrap the application).

And even if you do have a light server-side application--how do you securely inform the client (which still calls the authenticated 3rd party API itself) what the signature should be for every request it could possibly make? I'm very confused by how this is supposed to be designed from either end.

2
If you have users and need to prevent access to your resources (i.e AWS) and every request needs to be made from a SPA, you could use AWS Cognito, that's exactly why it was made for....user2976753

2 Answers

1
votes

I was going to write a long answer, but I think @Arjan covered everything in this Stack Overflow post. He and his team rolled their own solution, but he addresses the key concerns in REST authentication. Of course you can use something like OAuth, OpenID, or SAML depending on your situation. Here is a nice comparison of the three approaches.

Note the difference between the secret and the key. Also note that tokens need to be sent with each request because REST is stateless.

0
votes

I've done a few AngularJS apps and the way that I've found is to use an HttpModule like this one:

using System;
using System.Net.Http.Headers;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Web;

namespace YourSolution.WebApp.Modules
{
    public class BasicAuthenticationHttpModule : IHttpModule
    {
        public BasicAuthenticationHttpModule()
        {
        }

        public void Init(HttpApplication context)
        {
            context.AuthenticateRequest += OnApplicationAuthenticateRequest;
            context.EndRequest += OnApplicationEndRequest;
        }

        private static void SetPrincipal(IPrincipal principal)
        {
            Thread.CurrentPrincipal = principal;
            if (HttpContext.Current != null)
            {
                HttpContext.Current.User = principal;
            }
        }

        private static bool CheckPassword(
            string username, string password)
        {
            return username == password;
        }

        private static void AuthenticateUser(string credentials)
        {
            try
            {
                var encoding = Encoding.GetEncoding(
                    "iso-8859-1");
                credentials = encoding.GetString(
                    Convert.FromBase64String(credentials));

                var separator = credentials.IndexOf(':');
                var name = credentials.Substring(0, separator);
                var password = credentials.Substring(separator + 1);

                var validated = CheckPassword(name, password);
                if (!validated) return;

                var identity = new GenericIdentity(name);
                SetPrincipal(new GenericPrincipal(identity, null));
            }
            catch (FormatException)
            {
            }
        }

        private static void OnApplicationAuthenticateRequest(
            object sender, EventArgs e)
        {
            var request = HttpContext.Current.Request;
            var authHeader = request.Headers["Authorization"];

            if (authHeader == null) return;

            var authHeaderVal = AuthenticationHeaderValue.Parse(authHeader);

            if (authHeaderVal.Scheme.Equals(
                "basic",
                StringComparison.OrdinalIgnoreCase)
                && authHeaderVal.Parameter != null)
            {
                AuthenticateUser(authHeaderVal.Parameter);
            }
        }

        private static void OnApplicationEndRequest(
            object sender, EventArgs e)
        {
            var response = HttpContext.Current.Response;
            if (response.StatusCode == 401)
            {
                //response.Headers.Add(
                //    "WWW-Authenticate",
                //    string.Format("Basic realm=\"{0}\"", Realm));
            }
        }

        public void Dispose()
        {
        }
    }
}

The most important part is inside CheckPassword method, there is where you should validate the credentials.

Another point is this line response.Headers.Add("WWW-Authenticate", string.Format("Basic realm=\"{0}\"", Realm)); if you don't comment this line, the classic login requested form will show up, and if you do comment this line you have to catch the 401 error in your requests.

If you want to know about realm: What is the “realm” in basic authentication.

Plus, you will need to register the module in your web.config file:

<system.webServer>
    <modules>
        <add 
            name="BasicAuthenticationHttpModule"
            type="YourSolution.WebApp.Modules.BasicAuthenticationHttpModule" />
    </modules>
</system.webServer>

Then I've added these two methods to deal with the authentication token:

// u: username; p: password
CreateBasicAuthenticationToken = function (u, p) {
    var t = u + ':' + p;
    var hat = btoa(t);
    window.sessionStorage.setItem('basicauthtoken', 'basic ' + hat);
};

DestroyBasicAuthenticationToken = function () {
    window.sessionStorage.removeItem('basicauthtoken');
};

The btoa method: The btoa() method of window object is used to convert a given string to a encoded data (using base-64 encoding) string.. Taken from: http://www.w3resource.com/javascript/client-object-property-method/window-btoa.php.

And last I've added the authtoken to the request header using the beforeSend:

$.ajax({
    type: 'GET',
    url: 'your url',
    beforeSend: function (xhr) {
        window.sessionStorage.getItem('basicauthtoken');
    }
}).done(function (data, textStatus, xhr) {
    //...
});

Please do note using jQuery outside an angular directive is not recommended, AngularJS best practices dictates jQuery code must be always placed inside a directive.

Hope it helps.