Ltix handles the LTI 1.3 OIDC launch flow for tool applications. It is built around 4 main components:
Ltix.Registration— what the tool knows about a platform (issuer, client_id, endpoints). Created during out-of-band registrationLtix.StorageAdapter— behaviour your app implements to look up registrations, deployments, and manage noncesLtix.LaunchContext— the validated output of a successful launch, containing the parsed claims, registration, and deploymentLtix.LaunchClaims— structured data parsed from the ID Token (roles, context, resource link, and more)
Configuration
config :ltix,
storage_adapter: MyApp.LtiStorageAll configuration can also be passed (or overridden) per-call via opts.
Required
:storage_adapter— module implementingLtix.StorageAdapter. Looked up at runtime, so it works with releases.
Optional
:allow_anonymous— whentrue, allow launches without asubclaim in the ID Token. Defaults tofalse.:json_library— JSON encoder/decoder module. Detected at compile time: usesJSON(Elixir 1.18+/OTP 27+) if available, thenJason. Only set this if you need a different library.:req_options— default options passed toReq.request/2for all HTTP calls (JWKS fetching, OAuth token requests, service calls). Useful for setting timeouts, middleware, or test adapters:config :ltix, req_options: [receive_timeout: 10_000]:jwks_cache— module implementingLtix.JWT.KeySet.Cachefor caching platform public keys. Defaults toLtix.JWT.KeySet.EtsCache. ALtix.JWT.KeySet.CachexCacheadapter is also provided.:cachex_cache_name— Cachex cache name when usingLtix.JWT.KeySet.CachexCache. Defaults to:ltix_jwks.
Launch claim parsers
Custom claim and role parsers are configured under the
Ltix.LaunchClaims key:
config :ltix, Ltix.LaunchClaims,
claim_parsers: %{
"https://example.com/custom" => MyApp.CustomClaimParser
},
role_parsers: %{
"https://example.com/roles/" => MyApp.CustomRoleParser
}See Custom Claim Parsers and Custom Role Parsers for details.
Handling Launches
The LTI launch flow requires two endpoints. In your login endpoint,
call handle_login/3 with the platform's initiation params and your
launch URL:
def login(conn, params) do
launch_url = url(conn, ~p"/lti/launch")
{:ok, %{redirect_uri: url, state: state}} =
Ltix.handle_login(params, launch_url)
conn
|> put_session(:lti_state, state)
|> redirect(external: url)
endIn your launch endpoint, call handle_callback/3 with the POST
params and the stored state:
def launch(conn, params) do
state = get_session(conn, :lti_state)
{:ok, context} = Ltix.handle_callback(params, state)
# context.claims has the parsed launch data
# context.claims.target_link_uri is where to redirect
# context.claims.roles tells you who the user is
endAdvantage Services
After a successful launch, call platform services like roster queries and grade passback. Authenticate with the platform's token endpoint, then call service functions:
{:ok, client} = Ltix.MembershipsService.authenticate(context)
{:ok, roster} = Ltix.MembershipsService.get_members(client)
Enum.each(roster, fn member ->
IO.puts("#{member.name}: #{inspect(member.roles)}")
end)Post grades back to the platform's gradebook:
{:ok, client} = Ltix.GradeService.authenticate(context)
:ok = Ltix.GradeService.post_score(client, score)See the Advantage Services guide for OAuth details, token lifecycle, and multi-service authentication.
Deep Linking
When a platform sends an LtiDeepLinkingRequest launch, the same
handle_callback/3 returns a %LaunchContext{}. Branch on the
message type and build a response:
{:ok, context} = Ltix.handle_callback(params, state)
case context.claims.message_type do
"LtiDeepLinkingRequest" ->
{:ok, link} = Ltix.DeepLinking.ContentItem.LtiResourceLink.new(
url: "https://tool.example.com/activity/1",
title: "Quiz 1"
)
{:ok, response} = Ltix.DeepLinking.build_response(context, [link])
# POST response.jwt to response.return_url
"LtiResourceLinkRequest" ->
# Normal launch flow
endSee the Deep Linking guide for content item types, line items, and platform constraints.
Summary
Functions
Handle an authentication response and validate the ID Token.
Handle a platform's login initiation and build an authorization redirect.
Functions
@spec handle_callback(params :: map(), state :: String.t(), opts :: keyword()) :: {:ok, Ltix.LaunchContext.t()} | {:error, Exception.t()}
Handle an authentication response and validate the ID Token.
Returns {:ok, %LaunchContext{}} on success with parsed claims, registration,
and deployment. The state parameter should be the value stored in the
session during handle_login/2.
Use context.claims.target_link_uri for the final redirect destination.
Options
:storage_adapter— module implementingLtix.StorageAdapter(defaults to application config):allow_anonymous— allow launches without asubclaim (defaults to application config, thenfalse):req_options— options passed to the HTTP client for JWKS fetching:claim_parsers— custom claim parser modules (seeLtix.LaunchClaims.from_json/2):clock_skew— seconds of tolerance for token expiration (default:5)
@spec handle_login(params :: map(), redirect_uri :: String.t(), opts :: keyword()) :: {:ok, %{redirect_uri: String.t(), state: String.t()}} | {:error, Exception.t()}
Handle a platform's login initiation and build an authorization redirect.
The redirect_uri is the tool's launch URL where the platform will
POST the authentication response.
Returns {:ok, %{redirect_uri: url, state: state}} on success. Store
state in the user's session for CSRF verification, then redirect the
user agent to redirect_uri.
The nonce is stored via Ltix.StorageAdapter.store_nonce/2 automatically.
Options
:storage_adapter— module implementingLtix.StorageAdapter(defaults to application config)