Packages
An APIac authenticator plug for API authentication using the HTTP Bearer scheme
Retired package: Security issue - unsafe SSL handling (use of an insecure library)
Current section
Files
Jump to
Current section
Files
lib/validator/introspect.ex
defmodule APIacAuthBearer.Validator.Introspect do
@behaviour APIacAuthBearer.Validator
@moduledoc """
An implementation of client Bearer validation conforming to [RFC7662](https://tools.ietf.org/html/rfc7662)
This validator accepts the following options:
- `issuer`: an OAuth2 issuer whose metadata will be resolved by `OAuth2MetadataUpdater`.
When using this option, make sure you have added `OAuth2MetadataUpdater` in you `mix.exs` file.
The option value is one of:
- `"issueruri"`: metadata will be resolved directly on this URI
- `{"issueruri", opts}`: metadata will be resolved with `opts` passed as params to OAuth2MetadataUpdater
- `introspection_endpoint`: the URI of the introspection endpoint. Note that the `issuer`
option has precedence over the `introspection_endpoint` option
- `tesla_middlewares`: a list of [`Tesla.Middleware`s](https://hexdocs.pm/tesla/Tesla.Middleware.html#content)
that will sequentially be called before requesting the introspection endpoint.
Use it to authenticate to the OAuth2 authorization server.
## OAuth2MetadataUpdater
When using the `issueruri` option, make sure to include the `OAuth2MetadataUpdater`
library in the list of your dependencies, as the process or fetching metadata
information (including the `introspection_endpoint` parameter) is implemented
in that library and not in this one.
"""
@impl true
def validate(bearer, validator_opts) do
validator_opts = init_opts(validator_opts)
middlewares =
[
{Tesla.Middleware.FormUrlencoded, nil},
{Tesla.Middleware.Headers, [{"accept", "application/json"}]}
] ++ validator_opts[:tesla_middlewares]
# default httpc Tesla's adapter is unsafe (does not check TLS certificates)
http_client = Tesla.client(middlewares, Tesla.Adapter.Hackney)
req_body = [{"token", bearer}, {"token_type_hint", "access_token"}]
with {:ok, introspection_endpoint} <- introspection_endpoint(validator_opts),
:ok <- https?(introspection_endpoint),
{:ok, %Tesla.Env{status: 200, headers: headers, body: resp_body}} <-
Tesla.post(http_client, introspection_endpoint, req_body),
:ok <- valid_content_type?(headers),
{:ok, parsed_body} <- Jason.decode(resp_body) do
case parsed_body do
%{"active" => true} ->
{:ok, parsed_body}
_ ->
{:error, :invalid_token}
end
end
end
defp init_opts(validator_opts) do
if is_nil(validator_opts[:issuer]) and is_nil(validator_opts[:introspection_endpoint]) do
raise "#{__MODULE__}: missing `issuer` or `introspection_endpoint`"
end
Keyword.put_new(validator_opts, :tesla_middlewares, [])
end
defp introspection_endpoint(validator_opts) do
if not is_nil(validator_opts[:issuer]) do
{issuer_uri, oa2mu_opts} =
case validator_opts[:issuer] do
issuer_uri when is_binary(issuer_uri) ->
{issuer_uri, []}
{issuer_uri, validator_opts} ->
{issuer_uri, validator_opts}
end
case Oauth2MetadataUpdater.get_metadata_value(
issuer_uri,
"introspection_endpoint",
oa2mu_opts
) do
{:ok, nil} ->
{:error, :no_introspection_endpoint}
{:ok, introspection_endpoint} ->
{:ok, introspection_endpoint}
{:error, error} ->
{:error, error}
end
else
if is_binary(validator_opts[:introspection_endpoint]) do
validator_opts[:introspection_endpoint]
else
{:error, :no_introspection_endpoint}
end
end
end
defp https?(introspection_endpoint) do
case URI.parse(introspection_endpoint) do
%URI{scheme: "https"} ->
:ok
_ ->
{:error, :invalid_scheme_for_introspection_endpoint}
end
end
defp valid_content_type?(headers) do
if {"content-type", "application/json"} in headers do
:ok
else
{:error, :invalid_content_type_returned_by_introspection_endpoint}
end
end
end