Current section
Files
Jump to
Current section
Files
lib/graph_api/auth.ex
defmodule GraphApi.Auth do
@moduledoc """
Token acquisition for Microsoft Entra ID (Azure AD) using the client credentials flow.
Acquires OAuth 2.0 tokens by POSTing to the Entra ID token endpoint.
Tokens are typically managed by `GraphApi.TokenStore` rather than
calling this module directly.
"""
alias GraphApi.Config
alias GraphApi.Error.AuthError
@token_url_template "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token"
@type token_response :: %{
access_token: String.t(),
expires_in: integer(),
acquired_at: integer()
}
@doc """
Acquires an access token using the client credentials flow.
Returns `{:ok, token_response}` on success or `{:error, %AuthError{}}` on failure.
"""
@spec acquire_token(Config.t()) :: {:ok, token_response()} | {:error, AuthError.t()}
def acquire_token(%Config{} = config) do
url = token_url(config.tenant_id)
body = %{
"grant_type" => "client_credentials",
"client_id" => config.client_id,
"client_secret" => config.client_secret,
"scope" => config.scope
}
request = Req.new(url: url, form: body)
case Req.post(request) do
{:ok, %Req.Response{status: 200, body: body}} ->
{:ok,
%{
access_token: body["access_token"],
expires_in: body["expires_in"],
acquired_at: System.system_time(:second)
}}
{:ok, %Req.Response{status: status, body: body}} ->
{:error,
%AuthError{
status: status,
error: body["error"],
error_description: body["error_description"]
}}
{:error, exception} ->
{:error,
%AuthError{
status: 0,
error: "request_failed",
error_description: Exception.message(exception)
}}
end
end
@doc """
Checks whether a token has expired or will expire within the buffer period.
The default buffer is 300 seconds (5 minutes) to allow proactive refresh.
"""
@spec token_expired?(token_response(), integer()) :: boolean()
def token_expired?(token, buffer_seconds \\ 300) do
expires_at = token.acquired_at + token.expires_in - buffer_seconds
System.system_time(:second) >= expires_at
end
@doc false
def token_url(tenant_id) do
String.replace(@token_url_template, "{tenant}", tenant_id)
end
end