Skip to content

Commit

Permalink
added username_password flow
Browse files Browse the repository at this point in the history
  • Loading branch information
dvv committed Mar 22, 2013
1 parent ecc758b commit 8094b20
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 33 deletions.
19 changes: 18 additions & 1 deletion README.md
Expand Up @@ -18,21 +18,38 @@ Router configuration
-------------- --------------


```erlang ```erlang
% Authorize via Google
{"/auth/google/:action", cowboy_social, [ {"/auth/google/:action", cowboy_social, [
{provider, cowboy_social_google}, {provider, cowboy_social_google},
% At the end of the flow this handler will be called as % At the end of the flow this handler will be called as
% Mod:Fun({ok, Auth, Profile}, Req, State) or Mod:Fun({error, Reason}, Req, State) % Mod:Fun({ok, Auth, Profile}, Req, State) or Mod:Fun({error, Reason}, Req, State)
{handler, {Mod, Fun}}, {handler, {Mod, Fun}},
{client_id, <<"...">>}, {client_id, <<"...">>},
{client_secret, <<"...">>}, {client_secret, <<"...">>},
{scope, <<"https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile">>}, {scope, <<>>}, % additional permissions
{callback_uri, <<"/auth/google/callback">>} {callback_uri, <<"/auth/google/callback">>}
]}. ]}.

% In case of compliant provider we can just tune provider
{"/auth/good-provider/:action", cowboy_social, [
{provider, cowboy_social_generic},
{handler, {Mod, Fun}},
{client_id, <<"...">>},
{client_secret, <<"...">>},
{scope, <<>>}, % additional permissions
{callback_uri, <<"/auth/good-provider/callback">>},
% tune generic provider
{provider_name, <<"good-provider">>},
{authorize_url, <<"https://good.provider.org/oauth2/authorize">>},
{access_token_url, <<"https://good.provider.org/oauth2/access_token">>},
{profile_url, <<"https://good.provider.org/profile">>}
]}.
``` ```


Supported providers Supported providers
-------------- --------------
- Facebook - Facebook
- Generic one (no separate module required, just some parameters for the handler)
- Github - Github
- Google - Google
- Mail.ru - Mail.ru
Expand Down
89 changes: 57 additions & 32 deletions src/cowboy_social_provider.erl
Expand Up @@ -33,27 +33,11 @@ handle(Req, Opts) ->
handle_request(<<"authorize">>, Req, Opts) -> handle_request(<<"authorize">>, Req, Opts) ->
% extract parameters % extract parameters
{Data, Req2} = cowboy_req:qs_vals(Req), {Data, Req2} = cowboy_req:qs_vals(Req),
ClientId = key(<<"client_id">>, Data), case lists:keyfind(<<"client_id">>, 1, Data) of
RedirectUri = key(<<"redirect_uri">>, Data), false ->
% State = key(<<"state">>, Data), handle_flow(username_and_password, Data, Req2, Opts);
Scope = key(<<"scope">>, Data), _ ->
% redirect URI fits the client? handle_flow(client_id_and_secret, Data, Req2, Opts)
case verify_redirection_uri(ClientId, RedirectUri) of
% yes
ok ->
% issue authorization code
State = nonce(),
Code = termit:encode_base64({State, ClientId, RedirectUri, Scope},
key(code_secret, Opts)),
% show authorization form
{M, F} = key(authorization_form, Opts),
M:F(Code, RedirectUri, State, Req2);
% no
{error, mismatch} ->
% return error
cowboy_req:reply(302, [
{<<"location">>, << RedirectUri/binary, $?, "error=redirect_uri" >>}
], Req2)
end; end;


%% %%
Expand All @@ -71,7 +55,7 @@ handle_request(<<"access_token">>, Req, Opts) ->
% decode token and ensure its validity % decode token and ensure its validity
% @todo State instead of _ % @todo State instead of _
{ok, {_, ClientId, RedirectUri, Scope}} = {ok, {_, ClientId, RedirectUri, Scope}} =
% NB: code is expired after code_ttl seconds after issued % NB: code is expired after code_ttl seconds since issued
termit:decode_base64( termit:decode_base64(
key(<<"code">>, Data), key(<<"code">>, Data),
key(code_secret, Opts), key(code_secret, Opts),
Expand All @@ -81,14 +65,7 @@ handle_request(<<"access_token">>, Req, Opts) ->
{ok, Identity, Scope2} = {ok, Identity, Scope2} =
authorize_client_credentials(ClientId, ClientSecret, Scope), authorize_client_credentials(ClientId, ClientSecret, Scope),
% respond with token % respond with token
Token = termit:encode_base64({Identity, Scope2}, key(token_secret, Opts)), issue_token({Identity, Scope2}, Req2, Opts);
cowboy_req:reply(200, [
{<<"content-type">>, <<"application/json">>}
], jsx:encode([
{access_token, Token},
{token_type, <<"Bearer">>},
{expires_in, key(token_ttl, Opts)}
]), Req2);


%% %%
%% Catchall %% Catchall
Expand All @@ -97,6 +74,44 @@ handle_request(_, Req, _) ->
{ok, Req2} = cowboy_req:reply(404, [], <<>>, Req), {ok, Req2} = cowboy_req:reply(404, [], <<>>, Req),
{ok, Req2, undefined}. {ok, Req2, undefined}.


%%
%% Request access code for a client.
%%
handle_flow(client_id_and_secret, Data, Req, Opts) ->
ClientId = key(<<"client_id">>, Data),
RedirectUri = key(<<"redirect_uri">>, Data),
% State = key(<<"state">>, Data),
Scope = key(<<"scope">>, Data),
% redirect URI fits the client?
case verify_redirection_uri(ClientId, RedirectUri) of
% yes
ok ->
% generate authorization code
State = nonce(),
Code = termit:encode_base64(
{State, ClientId, RedirectUri, Scope},
key(code_secret, Opts)),
% show authorization form
{M, F} = key(authorization_form, Opts),
M:F(Code, RedirectUri, State, Req);
% no
{error, mismatch} ->
% return error
cowboy_req:reply(302, [
{<<"location">>, << RedirectUri/binary, $?, "error=redirect_uri" >>}
], Req)
end;

%%
%% Request access token for a user.
%%
handle_flow(username_and_password, Data, Req, Opts) ->
{ok, Identity, Scope} = authorize_username_password(
key(<<"username">>, Data),
key(<<"password">>, Data),
key(<<"scope">>, Data)),
issue_token({Identity, Scope}, Req, Opts).

%% %%
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Helpers %% Helpers
Expand All @@ -110,15 +125,25 @@ key(Key, List) ->
nonce() -> nonce() ->
base64:encode(crypto:strong_rand_bytes(16)). base64:encode(crypto:strong_rand_bytes(16)).


issue_token(Data, Req, Opts) ->
Token = termit:encode_base64(Data, key(token_secret, Opts)),
cowboy_req:reply(200, [
{<<"content-type">>, <<"application/json">>}
], jsx:encode([
{access_token, Token},
{token_type, <<"Bearer">>},
{expires_in, key(token_ttl, Opts)}
]), Req).

%% %%
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% OAuth2 backend functions %% OAuth2 backend functions
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% %%


% ok | {error, mismatch} % ok | {error, mismatch}
% authorize_username_password(Username, _Password, Scope) -> authorize_username_password(Username, _Password, Scope) ->
% {ok, {user, Username}, Scope}. {ok, {user, Username}, Scope}.


% ok | {error, mismatch} % ok | {error, mismatch}
authorize_client_credentials(ClientId, _ClientSecret, Scope) -> authorize_client_credentials(ClientId, _ClientSecret, Scope) ->
Expand Down

0 comments on commit 8094b20

Please sign in to comment.