Home

Awesome

Pragmatic Authentication Library

Build Status

The standardized multi-provider authentication library.

Introduction

PAL is designed to simplify an integration of authentication into web application. It can be used with any HTTP server and any developer can extend the library, create their own authentication workflow for everything from Facebook to LDAP. PAL is inspired by OmniAuth, Friend and Passport.

How to use

If you prefer to study the code rather documentation, the example below will show how to use the implementation of Google Login.
You also can find the complete example using PAL and Cowboy HTTP server here.

At first, you need to create the workflow. Workflow may have an required and optional options. We pass them and a name of the module with workflow implementation to the pal:new/2 function.

Options =
  #{client_id     => <<"...">>,
    client_secret => <<"...">>,
    redirect_uri  => <<"https://localhost/...">>}.

Workflow =
  pal:new(
    pal_google_oauth2_authcode,
    Options).

When our workflow was created, the half an job had been done. All we now need, parse the request and pass that data to the pal:authenticate/2 function.

pal:authenticate(Data, Workflow).

%% #{access_token => <<"...">>,
%%   token_type => <<"Bearer">>,
%%   expires_in => 3599,
%%   id_token => <<"...">>,
%%   code => <<"...">>}

That's all.

How it works

When a user come to us first time, the request doesn't contain any data. We have to redirect them to an authentication provider and we do it:

pal:authenticate(#{}, Workflow).

%% {stop,{resp,303, [{<<"location">>, <<"https://accounts.google.com/...">>}], <<>>}}

For Google Login (OAuth2 Authorization Code Grant) workflow we need to retrieve code, state and error fields from the query string of request if any of them appears, and pass that data to the pal:authenticate/2 function.

pal:authenticate(#{code => <<"...">>}, Workflow).

%% #{access_token => <<"...">>,
%%   token_type => <<"Bearer">>,
%%   expires_in => 3599,
%%   id_token => <<"...">>,
%%   code => <<"...">>}

Cowboy specific implementation parsing the data of request:

Data =
  lists:foldl(
    fun({Key, Val}, M) ->
      maps:put(binary_to_existing_atom(Key, utf8), Val, M)
    end,
    #{},
    pt_kvlist:with(
      [<<"code">>, <<"state">>, <<"error">>],
      cowboy_req:parse_qs(Req))).

Group

We can combine more than one workflow in the sequence. Below, we are using the access_token (result of the first workflow execution) to obtain the Google+ profile information.

Workflow =
  pal:group(
    [pal_google_oauth2_authcode, pal_google_plus_user],
    Options),

pal:authenticate(Data, Workflow).

%% #{uid => <<"...">>,
%%   info =>
%%     #{name => <<"John Doe">>,
%%       first_name => <<"John">>,
%%       last_name => <<"Doe">>,
%%       email => <<"john@example.com">>,
%%       gender => <<"male">>,
%%       image => <<"https://lh3.googleusercontent.com/...">>,
%%       uri => <<"https://plus.google.com/...">>},
%%   access_token => <<"...">>,
%%   token_type => <<"Bearer">>,
%%   expires_in => 3599,
%%   id_token => <<"...">>,
%%   code => <<"...">>}

Overview

Workflow is a fundamental unit of the library. It have to be defined as a module implementing at least the pal_workflow behaviour. Note that it is highly recommended to also implement the pal_authentication behaviour because it is responsible for the authentication schema creation flow.

Any workflow has a state. The pal:init/2 and pal:group/2 functions are responsible for its initialization. They expect in the arguments: a name of the module (list of module names in case of group) with the workflow implementation and an initial options of the workflow.

The workflow can be executed by calling pal:authenticate/{2,3} function. It expects in the arguments: parsed data were received with the request, optional data from any other source (server-side data) and the previously created state of workflow.

The result would contain data representing the authentication scheme {ok, NewData} or error with the reason {error, Reason} or a HTTP response {stop, HttpResp} must be passed back to a user to continue an authentication process. The error reason is represented by the tuple with the type and the error data in the workflow specific format (for instance, {oauth2, #{error => access_denied}}).

pal-authenticate

Authentication Schema

All keys of authentication schema are optional, but it is important to follow this structure always when it's possible.

List of workflows

ProviderWorkflowDescription
Googlepal_google_oauth2_authcodeGoogle Login (OAuth2 Authorization Code Grant)
Googlepal_google_openid_userGoogle OpenID Connect user's data
Googlepal_google_plus_userGoogle+ user's profile data
Facebookpal_facebook_oauth2_authcodeFacebook Login (OAuth2 Authorization Code Grant)
Facebookpal_facebook_userFacebook user's profile data
VKpal_vk_oauth2_authcodeVKontakte Login (OAuth2 Authorization Code Grant)
VKpal_vk_userVKontakte user's profile data
OKpal_ok_oauth2_authcodeOdnoklassniki Login (OAuth2 Authorization Code Grant)
OKpal_ok_userOdnoklassniki user's profile data
OAuth2pal_oauth2_authcodeOAuth2 Authorization Code Grant, RFC 6749
Behaviourpal_authenticationBehaviour of PAL workflow

License

The source code is provided under the terms of the MIT license.