Authenticated OAuth session for LTI Advantage service calls.
Holds an access token, tracks which scopes were granted, and provides
explicit refresh. Pass this struct to service functions like
Ltix.MembershipsService.get_members/2.
Checking expiry
client =
if Ltix.OAuth.Client.expired?(client) do
Ltix.OAuth.Client.refresh!(client)
else
client
endReusing tokens across contexts
A token is valid across contexts (courses, launches) on the same registration. Reuse a cached token with different endpoints:
{:ok, client_b} = Ltix.OAuth.Client.with_endpoints(client, %{
Ltix.MembershipsService => course_b_endpoint
})Or build from a previously cached AccessToken:
{:ok, client} = Ltix.OAuth.Client.from_access_token(cached_token,
registration: registration,
endpoints: %{Ltix.MembershipsService => endpoint}
)
Summary
Functions
Check whether the client's token has expired.
Build a client from a cached AccessToken.
Same as from_access_token/2 but raises on error.
Check whether the client was granted a specific scope.
Re-acquire the token using the stored registration and endpoints.
Same as refresh/1 but raises on error.
Require any one of the given scopes.
Require a specific scope, returning an error if not granted.
Swap endpoints on an existing client.
Same as with_endpoints/2 but raises on error.
Types
@type t() :: %Ltix.OAuth.Client{ access_token: String.t(), endpoints: %{required(module()) => term()}, expires_at: DateTime.t(), registration: Ltix.Registration.t(), req_options: keyword(), scopes: MapSet.t(String.t()) }
Functions
Check whether the client's token has expired.
Uses a 60-second buffer to avoid using a token that is about to expire.
Examples
iex> client = %Ltix.OAuth.Client{
...> access_token: "tok",
...> expires_at: DateTime.add(DateTime.utc_now(), 3600),
...> scopes: MapSet.new(),
...> registration: nil,
...> req_options: []
...> }
iex> Ltix.OAuth.Client.expired?(client)
false
@spec from_access_token( Ltix.OAuth.AccessToken.t(), keyword() ) :: {:ok, t()} | {:error, Exception.t()}
Build a client from a cached AccessToken.
Validates endpoints and checks that the token's granted scopes cover the required scopes for all endpoints.
Options
:registration(required) - theLtix.Registrationfor refresh:endpoints(required) - map of service modules to endpoint structs:req_options- options passed through toReq.request/2(default:[])
@spec from_access_token!( Ltix.OAuth.AccessToken.t(), keyword() ) :: t()
Same as from_access_token/2 but raises on error.
Check whether the client was granted a specific scope.
Examples
iex> client = %Ltix.OAuth.Client{
...> access_token: "tok",
...> expires_at: DateTime.utc_now(),
...> scopes: MapSet.new(["scope:read"]),
...> registration: nil,
...> req_options: []
...> }
iex> Ltix.OAuth.Client.has_scope?(client, "scope:read")
true
iex> Ltix.OAuth.Client.has_scope?(client, "scope:write")
false
@spec refresh(t()) :: {:ok, t()} | {:error, Exception.t()}
Re-acquire the token using the stored registration and endpoints.
Re-derives requested scopes from endpoints via each service's scopes/1
callback, so a transient partial grant does not become permanent.
Same as refresh/1 but raises on error.
@spec require_any_scope(t(), [String.t()]) :: :ok | {:error, Exception.t()}
Require any one of the given scopes.
Returns :ok if at least one scope from the list was granted.
@spec require_scope(t(), String.t()) :: :ok | {:error, Exception.t()}
Require a specific scope, returning an error if not granted.
@spec with_endpoints(t(), %{required(module()) => term()}) :: {:ok, t()} | {:error, Exception.t()}
Swap endpoints on an existing client.
Validates the new endpoints and checks that the client's granted scopes cover the required scopes. The token remains the same.
Same as with_endpoints/2 but raises on error.