Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SPA - Firebase and .Net WebApi 2 authentication

I'm having a Single Page Application written in AngularJs (The framework is irrelevant at this point) The application is hosted in IIS and it's compose of index.html plus a bunch of client assets.

On backend I have WebApi 2, hosted also in IIS as a separate application.

For authentication on client I'm using Firebase (simple login) with several social netoworks enabled, like Facebook, Twitter or Google.

So far so good. I like how easy it is to enable twitter authentication for example with firebase.

On login with social network i get back from firebase, the firebaseAuthToken and provider accesstoken.

Now I want to use firebaseAuthToken or provider access token to authenticate with my WebApi.

The question is: What is the best way to authenticate with WebApi in given conditions?

There is not an option to use only firebase to store my data and get rid of web api since I have in place complex business logic on server.

One silly idea that i have so far, is to pass the social provider access token to the server, validate the token against provider and then issue a security token using Owin -Katana.

I'm not using build in social providers support from katana due to lack of documentation, complexity and bad integration with single page apps. I found the visual studio template for SPA too mvc specific. But that's me :)

like image 672
czlatea Avatar asked Jul 14 '14 20:07

czlatea


1 Answers

tl;dr - Demo Project on GitHub

The steps below may seem long, but it's actually really easy. I created my demo project in just an hour or so.


I agree with you about using Owin and Katana. I've been through that process before and it wasn't really a great experience. Using Firebase was a heck of a lot easier.

This can all be done with JWTs!

When you authenticate through Firebase and whatever social provider, you get back a JSON Web Token (JWT) - firebaseAuthToken.

Grab your Firebase Secret from the Dashboard

The way JWTs work is that we have a secret token and a client token. The client token is the firebaseAuthToken we receive after logging in. The secret token is generated for us in the Firebase Dashboard.

Firebase JWTs Dashboard

Store your Firebase Secret in the appSettings section of your Web.config

We need to store this secret key in the Web.config so it's easier to access later.

<add key="FirebaseSecret" value="<Firebase-Secret-Token-Goes-Here" />

Create an Action Filter to check the JWT from the Authorization Header

We can verify the request is valid by passing the client token in the Authorization header. On the server we can store our secret key that we get from our Firebase Dashboard. When the request is checked by Web API we can decode the JWT using a JWT Library (available from NuGet). If the decoding is successful then we can check to token to make sure it isn't expired.

public class DecodeJWT: ActionFilterAttribute 
{

    public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext) 
    {
        string firebaseAuthToken = string.Empty;
        if (actionContext.Request.Headers.Authorization != null) {
            firebaseAuthToken = actionContext.Request.Headers.Authorization.Scheme;
        } else {
            throw new HttpException((int) HttpStatusCode.Unauthorized, "Unauthorized");
        }

        string secretKey = WebConfigurationManager.AppSettings["FirebaseSecret"];
        try {
            string jsonPayload = JWT.JsonWebToken.Decode(firebaseAuthToken, secretKey);
            DecodedToken decodedToken = JsonConvert.DeserializeObject < DecodedToken > (jsonPayload);
            // TODO: Check expiry of decoded token
        } catch (JWT.SignatureVerificationException jwtEx) {
            throw new HttpException((int) HttpStatusCode.Unauthorized, "Unauthorized");
        } catch (Exception ex) {
            throw new HttpException((int) HttpStatusCode.Unauthorized, "Unauthorized");
        }

        base.OnActionExecuting(actionContext);
    }

}

Create a $httpInterceptor add the firebaseAuthToken to the header for every request

On the client, the trick is that the token has to be passed every time. To make this easier we need to create a $httpInterceptor with Angular that checks for a firebaseAuthToken on sessionStorage.

.factory('authInterceptor', function ($rootScope, $q, $window) {
    return {
        request: function (config) {
            config.headers = config.headers || {};
            if ($window.sessionStorage.firebaseAuthToken) {
                config.headers.Authorization = $window.sessionStorage.firebaseAuthToken;
            }
            return config;
        },
        response: function (response) {
            if (response.status === 401) {
                // TODO: User is not authed
            }
            return response || $q.when(response);
        }
    };
})

Set the firebaseAuthToken to sessionStorage on a successful login

Whenever a user logs in we can set the value to sessionStorage.

$rootScope.$on('$firebaseSimpleLogin:login',
    function (e, user) {

        // add a cookie for the auth token
        if (user) {
            $window.sessionStorage.firebaseAuthToken = user.firebaseAuthToken;
        }

        cb(e, user);
    });

Register the DecodeJWT filter globally

Inside of the WebApiConfig.cs Register method we can set the DecodeJWT filter to apply for all of our ApiControllers.

config.Filters.Add(new DecodeJWT());

Now whenever we make a request to an ApiController it will reject it unless there is a valid JWT. So after a user logs in we can save their data to a ApiController if it already doesn't exist.

// globally uses DecodeJWT
public class UsersController: ApiController 
{
    // POST api/users
    public void Post([FromBody] FbUser user) // See GitHub for this Model
    {
        // Save user if we do not already have it
    }
}
like image 200
David East Avatar answered Oct 18 '22 19:10

David East