Packages
OpenId Connect cowboy handler
Retired package: Deprecated - This library is no longer maintained. Reach out to the EEF Security WG in case you would like to maintain this library.
Current section
Files
Jump to
Current section
Files
src/oidcc_cowboy_load_userinfo.erl
%%%-------------------------------------------------------------------
%% @doc Validate extracted authorization token using userinfo retrieval.
%%
%% See: [https://openid.net/specs/openid-connect-core-1_0.html#UserInfo]
%%
%% This middleware should be used together with
%% {@link oidcc_cowboy_extract_authorization}.
%%
%% This middleware will send a userinfo request for ever request. To avoid this,
%% provide a `cache' to {@link opts()}.
%%
%% <h2>Usage</h2>
%%
%% ```
%% OidccCowboyOpts = #{
%% provider => openid_confi_provider_name,
%% client_id => <<"client_id">>,
%% client_secret => <<"client_secret">>
%% },
%% Dispatch = cowboy_router:compile([
%% {'_', [
%% %% ...
%% ]}
%% ]),
%% {ok, _} = cowboy:start_clear(http, [{port, 8080}], #{
%% middlewares => [
%% oidcc_cowboy_extract_authorization,
%% oidcc_cowboy_load_userinfo,
%% cowboy_router,
%% cowboy_handler
%% ],
%% env => #{
%% dispatch => Dispatch,
%% oidcc_cowboy_load_userinfo => OidccCowboyOpts
%% }
%% })
%% '''
%% @end
%% @since 2.0.0
%%%-------------------------------------------------------------------
-module(oidcc_cowboy_load_userinfo).
-behaviour(cowboy_middleware).
-export([execute/2]).
-export_type([opts/0]).
-type opts() :: #{
provider := gen_server:server_ref(),
client_id := binary(),
client_secret := binary(),
userinfo_retrieve_opts => oidcc_userinfo:retrieve_opts(),
cache => oidcc_cowboy_cache:t(),
send_inactive_token_response => fun(
(Req :: cowboy_req:req(), Env :: cowboy_middleware:env()) ->
{ok, cowboy_req:req(), cowboy_middleware:env()} | {stop, cowboy_req:req()}
)
}.
%% Options for the middleware
%%
%% <h2>Options</h2>
%%
%% <ul>
%% <li>`provider' - name of the
%% {@link oidcc_provider_configuration_worker}</li>
%% <li>`client_id' - OAuth Client ID to use for the userinfo retrieval</li>
%% <li>`client_secret' - OAuth Client Secret to use for the userinfo
%% retrieval</li>
%% <li>`userinfo_retrieve_opts' - Options to pass to userinfo loading</li>
%% <li>`send_inactive_token_response' - Customize Error Response for inactive
%% token</li>
%% <li>`cache' - Cache userinfo response - See {@link oidcc_cowboy_cache}</li>
%% </ul>
%% @private
execute(#{oidcc_cowboy_extract_authorization := undefined} = Req, #{?MODULE := _Opts} = Env) ->
{ok, maps:put(?MODULE, undefined, Req), Env};
execute(#{oidcc_cowboy_extract_authorization := Token} = Req, #{?MODULE := Opts} = Env) ->
Provider = maps:get(provider, Opts),
ClientId = maps:get(client_id, Opts),
ClientSecret = maps:get(client_secret, Opts),
UserinfoRetrieveOpts0 = maps:get(userinfo_retrieve_opts, Opts, #{}),
UserinfoRetrieveOpts = maps:put(expected_subject, any, UserinfoRetrieveOpts0),
SendInactiveTokenResponse = maps:get(
send_inactive_token_response, Opts, fun send_inactive_token_response/2
),
Cache = maps:get(cache, Opts, oidcc_cowboy_cache_noop),
case Cache:get(userinfo, Token, Req, Env) of
{ok, #{} = Claims} ->
{ok, maps:put(?MODULE, Claims, Req), Env};
miss ->
case
oidcc:retrieve_userinfo(
Token, Provider, ClientId, ClientSecret, UserinfoRetrieveOpts
)
of
{ok, #{} = Claims} ->
Cache:put(userinfo, Token, Claims, Req, Env),
{ok, maps:put(?MODULE, Claims, Req), Env};
{error, {http_error, 401, _Body}} ->
SendInactiveTokenResponse(maps:put(?MODULE, undefined, Req), Env);
{error, Reason} ->
erlang:error(Reason)
end
end;
execute(#{oidcc_cowboy_extract_authorization := _Token} = _Req, #{} = _Env) ->
erlang:error(no_config_provided);
execute(#{} = _Req, #{?MODULE := _Opts} = _Env) ->
erlang:error(no_oidcc_cowboy_extract_authorization).
send_inactive_token_response(Req0, _Env) ->
Req = cowboy_req:reply(
401,
#{<<"content-type">> => <<"text/plain">>},
<<"The provided token is inactive">>,
Req0
),
{stop, Req}.