Packages
boruta
2.2.1
3.0.0-beta.4
3.0.0-beta.3
3.0.0-beta.2
3.0.0-beta.1
2.3.6
2.3.5
2.3.4
2.3.3
2.3.2
2.3.1
2.3.0
2.2.2
2.2.1
2.2.0
2.1.5
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.0.1
2.0.0
2.0.0-rc.1
2.0.0-rc.0
1.2.1
1.2.0
1.1.0
1.0.3
1.0.2
1.0.1
1.0.0
1.0.0-rc.3
1.0.0-rc.2
1.0.0-rc.1
1.0.0-rc.0
0.2.1
0.2.0
0.1.1
0.1.0
0.1.0-rc.5
0.1.0-rc.4
0.1.0-rc.3
0.1.0-rc.2
0.1.0-rc.1
Core of an OAuth/OpenID Connect provider enabling authorization in your applications.
Current section
Files
Jump to
Current section
Files
lib/boruta/oauth/schemas/id_token.ex
defmodule Boruta.Oauth.IdToken do
@moduledoc """
OpenID Connect id token schema and utilities
"""
defmodule Token do
@moduledoc false
use Joken.Config
def token_config, do: %{}
end
import Boruta.Config, only: [resource_owners: 0, issuer: 0]
alias Boruta.Oauth
@type claims :: %{
String.t() => String.t() | claims()
}
@signature_algorithms [
RS256: [type: :asymmetric, hash_algorithm: :SHA256, binary_size: 16],
RS384: [type: :asymmetric, hash_algorithm: :SHA384, binary_size: 24],
RS512: [type: :asymmetric, hash_algorithm: :SHA512, binary_size: 32],
HS256: [type: :symmetric, hash_algorithm: :SHA256, binary_size: 16],
HS384: [type: :symmetric, hash_algorithm: :SHA384, binary_size: 24],
HS512: [type: :symmetric, hash_algorithm: :SHA512, binary_size: 32]
]
@spec signature_algorithms() :: list(atom())
def signature_algorithms, do: Keyword.keys(@signature_algorithms)
@spec signature_type(Oauth.Client.t()) :: signature_type :: atom()
def signature_type(%Oauth.Client{id_token_signature_alg: signature_alg}),
do: @signature_algorithms[String.to_atom(signature_alg)][:type]
@spec hash_alg(Oauth.Client.t()) :: hash_alg :: atom()
def hash_alg(%Oauth.Client{id_token_signature_alg: signature_alg}),
do: @signature_algorithms[String.to_atom(signature_alg)][:hash_algorithm]
@spec hash_binary_size(Oauth.Client.t()) :: binary_size :: integer()
def hash_binary_size(%Oauth.Client{id_token_signature_alg: signature_alg}),
do: @signature_algorithms[String.to_atom(signature_alg)][:binary_size]
@type tokens :: %{
optional(:code) => %Oauth.Token{
sub: String.t(),
client: Oauth.Client.t(),
inserted_at: DateTime.t(),
scope: String.t()
},
optional(:token) => %Oauth.Token{
sub: String.t(),
client: Oauth.Client.t(),
inserted_at: DateTime.t(),
scope: String.t()
},
optional(:base_token) => %Oauth.Token{
sub: String.t(),
client: Oauth.Client.t(),
inserted_at: DateTime.t(),
scope: String.t()
}
}
@spec generate(tokens :: tokens(), nonce :: String.t()) :: id_token :: Oauth.Token.t()
def generate(tokens, nonce) do
{base_token, payload} = payload(tokens, nonce, %{})
value = sign(payload, base_token.client)
%{base_token | type: "id_token", value: value}
end
defp payload(%{code: code} = tokens, nonce, acc) do
tokens
|> Map.put(:base_token, code)
|> Map.delete(:code)
|> payload(nonce, Map.put(acc, "c_hash", hash(code.value, code.client)))
end
defp payload(%{token: token} = tokens, nonce, acc) do
tokens
|> Map.put(:base_token, token)
|> Map.delete(:token)
|> payload(nonce, Map.put(acc, "at_hash", hash(token.value, token.client)))
end
defp payload(%{base_token: base_token}, nonce, acc) do
{base_token, Map.merge(acc, payload(base_token, nonce))}
end
defp payload(
%Oauth.Token{
sub: sub,
client: client,
inserted_at: inserted_at,
scope: scope,
resource_owner: resource_owner
},
nonce
) do
iat = DateTime.to_unix(inserted_at)
auth_time =
case resource_owner.last_login_at do
nil -> :os.system_time(:seconds)
last_login_at -> DateTime.to_unix(last_login_at)
end
resource_owners().claims(resource_owner, scope)
|> Map.merge(resource_owner.extra_claims)
|> Map.put("sub", sub)
|> Map.put("iss", issuer())
|> Map.put("aud", client.id)
|> Map.put("iat", iat)
|> Map.put("auth_time", auth_time)
|> Map.put("exp", iat + client.id_token_ttl)
|> Map.put("nonce", nonce)
end
defp sign(
payload,
%Oauth.Client{
id: client_id,
id_token_signature_alg: signature_alg,
private_key: private_key,
secret: secret
} = client
) do
signer =
case signature_type(client) do
:symmetric ->
Joken.Signer.create(signature_alg, secret)
:asymmetric ->
Joken.Signer.create(signature_alg, %{"pem" => private_key}, %{"kid" => client_id})
end
with {:ok, token, _payload} <- Token.encode_and_sign(payload, signer) do
token
end
end
defp hash(string, client) do
hash_alg(client)
|> Atom.to_string()
|> String.downcase()
|> String.to_atom()
|> :crypto.hash(string)
|> binary_part(0, hash_binary_size(client))
|> Base.url_encode64(padding: false)
end
end