OAuth2 integration library for Elixir applications.
Provides OAuth2 Authorization Code Flow with PKCE support, multi-tenant token management, and provider configuration.
Ready-to-Use API
For Phoenix applications, Tango provides a complete OAuth API that can be mounted with a single line:
# In your Phoenix router
scope "/api/oauth" do
pipe_through :api
forward "/", Tango.API.Router
endSee Tango.API for complete setup and usage documentation.
Direct Library Usage
You can also use Tango's functions directly in your application code:
Summary
Functions
Generates OAuth authorization URL with PKCE support.
Deletes expired and consumed authorization candidates.
Cleans up expired OAuth sessions.
Creates an authorization session with server-bound intent and completion data.
Creates a new provider.
Creates provider from Nango configuration.
Creates OAuth session for provider and tenant.
Soft deletes a provider.
Exchanges authorization code for access token.
Gets an active connection by ID with tenant isolation and optional refresh.
Gets connection for provider and tenant.
Gets provider by name.
Gets provider by ID.
Gets OAuth session by token.
Lists active connections for a tenant.
Lists active OAuth providers.
Marks connection as used (updates timestamp).
Adds atomic authorization finalization to a consumer-owned Ecto.Multi.
Records the provider rejecting a connection's credentials.
Clears a connection's rejection streak after a call authenticates again.
Refreshes an OAuth connection's access token.
Refreshes connections about to expire.
Revokes a connection for a tenant.
Exchanges an authorization code into an encrypted, short-lived candidate.
Updates a provider.
Verifies a staged authorization candidate with a trusted server callback.
Verifies and conditionally binds the identity of an active connection.
Functions
Generates OAuth authorization URL with PKCE support.
Examples
iex> Tango.authorize_url("session_token", redirect_uri: "https://app.com/callback")
{:ok, "https://provider.com/oauth/authorize?..."}
Deletes expired and consumed authorization candidates.
Returns {:ok, deleted_count}. Token columns are wiped before the rows are
deleted, and the audit entry records only the count and a cleanup category.
Tango does not start a scheduler. Schedule this from the consuming
application's own job runner (Oban, Quantum, a GenServer timer, ...) together
with Tango.cleanup_expired_sessions/0, which is also unscheduled by default.
Run this function first so candidate credentials are wiped before the sessions
they belong to are deleted:
def perform(_job) do
{:ok, _candidates} = Tango.cleanup_expired_authorizations()
{:ok, _sessions} = Tango.cleanup_expired_sessions()
:ok
endRunning it every few minutes keeps the candidate table small, since candidates live for at most ten minutes.
Cleans up expired OAuth sessions.
Examples
iex> Tango.cleanup_expired_sessions()
{:ok, 5} # 5 sessions cleaned up
Creates an authorization session with server-bound intent and completion data.
Creates a new provider.
Examples
iex> Tango.create_provider(%{name: "custom", display_name: "Custom OAuth"})
{:ok, %Tango.Schemas.Provider{}}
Creates provider from Nango configuration.
Examples
iex> Tango.create_provider_from_nango("github", nango_config, client_id: "abc", client_secret: "xyz")
{:ok, %Tango.Schemas.Provider{}}
Creates OAuth session for provider and tenant.
Returns session that can be used to generate authorization URL.
Examples
iex> Tango.create_session("github", "user-123")
{:ok, %Tango.Schemas.OAuthSession{}}
iex> Tango.create_session("nonexistent", "user-123")
{:error, :provider_not_found}
Soft deletes a provider.
Examples
iex> Tango.delete_provider(provider)
{:ok, %Tango.Schemas.Provider{active: false}}
Exchanges authorization code for access token.
Validates session state, exchanges code with provider, and creates connection with multi-tenant isolation.
Examples
iex> Tango.exchange_code("state", "code", "tenant-123", redirect_uri: "https://app.com/callback")
{:ok, %Tango.Schemas.Connection{}}
Gets an active connection by ID with tenant isolation and optional refresh.
Gets connection for provider and tenant.
Examples
iex> Tango.get_connection_for_provider("github", "user-123")
{:ok, %Tango.Schemas.Connection{}}
iex> Tango.get_connection_for_provider("github", "user-123", auto_refresh: true)
{:ok, %Tango.Schemas.Connection{}} # Automatically refreshed if needed
Gets provider by name.
Examples
iex> Tango.get_provider("github")
{:ok, %Tango.Schemas.Provider{}}
Gets provider by ID.
Examples
iex> Tango.get_provider_by_id(provider_id)
{:ok, %Tango.Schemas.Provider{}}
Gets OAuth session by token.
Examples
iex> Tango.get_session("session_token")
{:ok, %Tango.Schemas.OAuthSession{}}
Lists active connections for a tenant.
Examples
iex> Tango.list_connections("user-123")
[%Tango.Schemas.Connection{}, ...]
Lists active OAuth providers.
Examples
iex> Tango.list_providers()
[%Tango.Schemas.Provider{}, ...]
Marks connection as used (updates timestamp).
Examples
iex> Tango.mark_connection_used(connection)
{:ok, %Tango.Schemas.Connection{}}
Adds atomic authorization finalization to a consumer-owned Ecto.Multi.
Records the provider rejecting a connection's credentials.
Call this when a provider rejects the connection's credentials outright — an
HTTP 401, or a 403 whose provider-specific error body confirms an invalid or
revoked token rather than a policy, scope, or quota denial. Tango cannot see
the calls made with the tokens it issues, so a revoked credential stays
:active until a consumer reports the rejection. The connection is expired
once the rejections reach opts[:max_failures] (default 3), after which
get_connection_for_provider/2 returns {:error, :not_found}. Pass a
threshold matched to how often the caller hits that provider — the count is a
number of rejections, not a duration.
Accepts a connection, or a connection id together with opts[:tenant_id].
Only active connections accept a report.
Examples
iex> Tango.record_auth_failure(connection, "http_401")
{:ok, %Tango.Schemas.Connection{auth_failures: 1}}
iex> Tango.record_auth_failure(connection_id, "http_401", tenant_id: "user-123", max_failures: 10)
{:ok, %Tango.Schemas.Connection{auth_failures: 1}}
Clears a connection's rejection streak after a call authenticates again.
Pairs with record_auth_failure/3 to keep its count consecutive. Accepts a
connection, or a connection id together with opts[:tenant_id].
Examples
iex> Tango.record_auth_success(connection)
:ok
Refreshes an OAuth connection's access token.
Examples
iex> Tango.refresh_connection(connection)
{:ok, %Tango.Schemas.Connection{}}
Refreshes connections about to expire.
Examples
iex> Tango.refresh_expiring_connections()
{:ok, 3} # 3 connections refreshed
Revokes a connection for a tenant.
Examples
iex> Tango.revoke_connection(connection, "user-123")
{:ok, %Tango.Schemas.Connection{status: :revoked}}
Exchanges an authorization code into an encrypted, short-lived candidate.
Updates a provider.
Examples
iex> Tango.update_provider(provider, %{display_name: "New Name"})
{:ok, %Tango.Schemas.Provider{}}
Verifies a staged authorization candidate with a trusted server callback.
Verifies and conditionally binds the identity of an active connection.