Ltix parses standard OIDC and LTI claims from the launch JWT
automatically. Unusable optional metadata becomes nil, while valid
fields remain available. Identity and required launch fields remain
validated. Any unrecognized claims are put in the extensions map as raw
values.
context.claims.extensions
#=> %{"https://myplatform.example.com/claim/analytics" => %{"session_id" => "abc123"}}Register custom claim parsers to transform these raw values into structured data.
Writing a claim parser
A claim parser is a module implementing the Ltix.LaunchClaims.Parser behavior. Its parse/1 callback takes the raw claim value and returns {:ok, parsed} or {:error, reason}.
defmodule MyApp.Lti.AnalyticsClaim do
use Ltix.LaunchClaims.Parser
defstruct [:session_id, :tracking_enabled]
@impl Ltix.LaunchClaims.Parser
def parse(%{"session_id" => session_id} = raw) do
{:ok,
%__MODULE__{
session_id: session_id,
tracking_enabled: Map.get(raw, "tracking_enabled", false)
}}
end
def parse(_), do: {:error, "missing session_id in analytics claim"}
endA 1-arity function with the same contract works too, such as fn raw -> {:ok, raw} end.
Registering parsers
Claim parsers are registered as a map of claim key to parser module or function. The key is the full claim name as it appears in the JWT (usually a namespaced URI for extension claims).
Via application config (recommended for parsers that apply globally)
# config/config.exs
config :ltix, Ltix.LaunchClaims,
claim_parsers: %{
"https://myplatform.example.com/claim/analytics" => MyApp.Lti.AnalyticsClaim
}Via handle_callback/3 (for per-call control)
Ltix.handle_callback(params, state,
claim_parsers: %{
"https://myplatform.example.com/claim/analytics" => MyApp.Lti.AnalyticsClaim
}
)Via LaunchClaims.from_json/2 (for direct parsing)
Ltix.LaunchClaims.from_json(claims,
claim_parsers: %{
"https://myplatform.example.com/claim/analytics" => MyApp.Lti.AnalyticsClaim
}
)Per-call parsers merge with application config, with per-call taking priority for overlapping keys.
To replace a standard parser, register its claim key. The parsed value
appears in the corresponding LaunchClaims field.
Accessing parsed claims
Parsed extension claims remain in the extensions map, and their
values are your parsed values.
{:ok, context} = Ltix.handle_callback(params, state)
context.claims.extensions["https://myplatform.example.com/claim/analytics"]
#=> %MyApp.Lti.AnalyticsClaim{session_id: "abc123", tracking_enabled: true}Missing claims
A parser runs only for claims present in the token. An absent extension claim is left out of extensions, and an absent standard claim leaves its LaunchClaims field nil. A claim sent as JSON null is passed to the parser as nil.
Error handling
When a parser returns {:error, reason}, the launch fails with Ltix.Errors.Invalid.InvalidClaim, whose message is the reason (a string as given, an exception's message, or any other term as inspect/1 shows it). A reason that is already an Ltix error is returned unchanged.
{:error, %Ltix.Errors.Invalid.InvalidClaim{} = error} = Ltix.handle_callback(params, state)
error.claim
#=> "https://myplatform.example.com/claim/analytics"
error.message
#=> "missing session_id in analytics claim"Registering multiple parsers
You can register claim parsers and role parsers together in application config.
config :ltix, Ltix.LaunchClaims,
role_parsers: %{
"https://myplatform.example.com/roles/" => MyApp.Lti.PlatformRoleParser
},
claim_parsers: %{
"https://myplatform.example.com/claim/analytics" => MyApp.Lti.AnalyticsClaim,
"https://myplatform.example.com/claim/proctoring" => MyApp.Lti.ProctoringClaim
}Next steps
- Custom Role Parsers for handling proprietary role vocabularies
Ltix.LaunchClaimsfor the full claims struct andfrom_json/2options- Error Handling for how parser errors are returned