Authentication with cowboy & stormpath

I’ve been meaning to try out an “authentication as a service” offering for a little while now. The first one I had come across was Auth0, which looks very slick, particularly if you use their “Lock” frontend.

Unfortunately, their documentation didn’t seem to have much information on what to do if you’re using a language they don’t provide an SDK for. I’m sure I could have deconstructed the node version, but I decided to try out Stormpath instead.

My weapon of choice at the moment is Cowboy, which is quite barebones; more of an http toolkit than a web framework (whether this is good or bad has been discussed, at length, elsewhere). To make things more interesting, I also wanted to support a mixture of secure and insecure content.

I chose to use an existing solution for session management, rather than re-inventing all the wheels at once; and to use Hackney (from an ever expanding roster of clients) for http requests.

Users need to be able to login:

handle_req(<<"POST">>, Req) ->
    {ok, Body, Req2} = cowboy_req:body_qs(Req),
    Email = proplists:get_value(<<"email">>, Body),
    Password = proplists:get_value(<<"password">>, Body),
    case stormpath:login(Email, Password) of
        {ok, UserInfo} ->
            {ok, Req3} = cowboy_session:set(<<"user">>, UserInfo, Req2),
            redirect_to(<<"/user/info">>, Req3);
        {error, Error} ->
            render_login_page([{error, Error}, {email, Email}], Req2)
    end;

which calls the Stormpath API:

login(Email, Password) ->
    Uri = get_stormpath_uri(<<"/loginAttempts">>),
    ReqBody = #{type => <<"basic">>, value => base64:encode(<<Email/binary, ":", Password/binary>>)},
    ReqJson = jiffy:encode(ReqBody),
    {ok, StatusCode, _, ClientRef} = stormpath_request(post, Uri, ReqJson),
    {ok, RespBody} = hackney:body(ClientRef),
    RespJson = jiffy:decode(RespBody, [return_maps]),
    case StatusCode of
        200 ->
            get_user_info(RespJson);
        400 ->
            get_error_message(RespJson);
        _ ->
            generic_error()
    end.

It’s then possible to check if a user is logged in:

execute(Req, Env) ->
    {Path, _} = cowboy_req:path(Req),
    check_path(Path, Req, Env).

check_path(<<"/user/", _/binary>>, Req, Env) ->
    case logged_in(Req) of
        false ->
            redirect_to(<<"/login">>, Req);
        true ->
            {ok, Req, Env}
    end;

check_path(_, Req, Env) ->
    {ok, Req, Env}.

logged_in(Req) ->
    {User, _} = cowboy_session:get(<<"user">>, Req),
    case User of
        undefined -> false;
        _ -> true
    end.

redirect_to(Location, Req) ->
    {ok, Req2} = cowboy_req:reply(302, [{<<"Location">>, Location}], Req),
    {halt, Req2}.

before processing a request; and bouncing them to the login page if they aren’t, when they should be.

You can find the source here. I think using one of these services could definitely save you some time at the start of a new project, as well as removing the risk of making common security blunders. On the other hand you take on their uptime as well as your own, and you are locked in to using their service should the pricing become unattractive (although Stormpath, at least, allows you to export your data). YMMV.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s