Packages

Elixir client for the Microsoft Graph API

Current section

Files

Jump to
keen_microsoft_graphapi lib graph_api auth.ex
Raw

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