Erlang OAuth2, an implementation

1 minute read

In the previous 2 articles (here and here) I wrote about the implementation of JWT in the OAuth2 library. Today I want to talk about the actual code needed to implement JWT in the library.

Last night I finished a first iteration of this work. The amount of changes were not stellar, but the fact that I needed to change some internals to pass along the User that is connected at the time does not sit well at this time. I will revisit this code and make sure that it is actually necessary.

The mandatory changes, as you might remember, were to retrieve the claims the user has from the backend. This was easily accomplished by adding another callback to the backend definition:

1
2
3
%% @doc Retrieve a list of claims for this user
-callback retrieve_user_claims(user(), appctx()) -> {ok, {appctx(), [claim()]}}
                                                  | {error, notfound}.

As you might remember from the Architecture article, the generation of tokens is done by implementing a Token Generation behaviour.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
-spec generate(oauth2:user(), oauth2:context()) -> oauth2:token().
generate(User, Context) -> 
    Header = create_header(Context),
    Payload = create_payload(User, Context),
    Token = create_token(Context, Header, Payload),
    Token.

-spec create_header(oauth2:context()) -> binary().
create_header(_Context) ->
    base64url:encode(jiffy:encode({[{alg, <<"HS512">>},{typ, <<"JWT">>}]})).

-spec create_payload(oauth2:user(), oauth2:context()) -> binary().
create_payload(User, Context) ->
    Issued = oauth2:seconds_since_epoch(0),
    Expiry = case oauth2:get(Context, <<"expiry_time">>) of
		 {ok, Value} -> Value;
		 {error, notfound} -> oauth2:seconds_since_epoch(oauth2_config:expiry_time())
	     end,
    Claims = case ?BACKEND:retrieve_user_claims(User, Context) of
		 {ok, {_Ctx, Cls}} -> Cls;  
		 {error, notfound} -> []
	     end,
    Doc = {[{exp, Expiry}, {iat, Issued}] ++ Claims},
    Json = jiffy:encode(Doc),
    base64url:encode(Json).

-spec create_token(oauth2:context(), binary(), binary()) -> binary().
create_token(_Context, Header, Payload) ->
    Packet = <<Header/binary, ".", Payload/binary>>,
    Signature = base64url:encode(hmac:hmac512("secret", Packet)),
    <<Packet/binary, ".", Signature/binary>>.

There are still many things that need to be enhanced in this code, such as the configurability of the algorithm used and the standard claims that are passed to the client. But for now it is a decent start.

Next up is to enhance the functionality and to utilize the library in my project.