Tango includes a complete OAuth API router that can be mounted in Phoenix applications with a single line.
Setup
- Configure API key in your application:
# config/config.exs
config :tango,
encryption_key: System.get_env("TANGO_ENCRYPTION_KEY"),
api_key: System.get_env("TANGO_API_KEY")- Add the API router to your Phoenix router:
# router.ex
defmodule MyAppWeb.Router do
use MyAppWeb, :router
scope "/api/oauth" do
pipe_through :api
forward "/", Tango.API.Router
end
endAvailable Endpoints
The mounted API provides these endpoints:
POST /api/oauth/sessions- Create OAuth sessionGET /api/oauth/authorize/:session_token- Get authorization URLPOST /api/oauth/exchange- Exchange authorization code for a connection (direct flow; returns status and scopes, never credentials)GET /api/oauth/callback- Popup callback; posts the completion handle to the session's completion originGET /api/oauth/health- Health check
Calling the API
This example uses the direct flow with /sessions, /authorize, and /exchange. For browser popups, use authorization completion.
Warning:
TANGO_API_KEYis a server secret. Never put it in browser JavaScript; call Tango's API only from your backend.
The browser talks to your own backend, which authenticates the user, derives the tenant from its session, and calls Tango's API server-side with the key:
# Your backend (e.g. a Phoenix controller). The tenant comes from your own
# authenticated session, never from the browser.
defmodule MyAppWeb.IntegrationController do
use MyAppWeb, :controller
@tango_api "https://yourapp.com/api/oauth"
@redirect_uri "https://yourapp.com/integrations/callback"
def connect(conn, %{"provider" => provider}) do
tenant_id = to_string(conn.assigns.current_user.id)
with {:ok, %{status: 201, body: session}} <-
Req.post("#{@tango_api}/sessions",
headers: tango_headers(tenant_id),
json: %{provider: provider, redirect_uri: @redirect_uri}
),
{:ok, %{status: 200, body: %{"authorization_url" => url}}} <-
Req.get("#{@tango_api}/authorize/#{session["session_token"]}",
headers: tango_headers(tenant_id),
params: [redirect_uri: @redirect_uri]
) do
redirect(conn, external: url)
else
_ -> send_resp(conn, 502, "Could not start OAuth")
end
end
def callback(conn, %{"state" => state, "code" => code}) do
tenant_id = to_string(conn.assigns.current_user.id)
case Req.post("#{@tango_api}/exchange",
headers: tango_headers(tenant_id),
json: %{state: state, code: code, redirect_uri: @redirect_uri}
) do
{:ok, %{status: 200}} -> redirect(conn, to: "/integrations")
_ -> send_resp(conn, 400, "OAuth exchange failed")
end
end
defp tango_headers(tenant_id) do
[
{"authorization", "Bearer #{System.fetch_env!("TANGO_API_KEY")}"},
{"x-tenant-id", tenant_id}
]
end
endThe browser only navigates to your backend's routes and never sees the API key:
// Start OAuth: your backend creates the session and redirects to the provider.
function startOAuth(provider) {
window.location.href = `/integrations/${encodeURIComponent(provider)}/connect`;
}If your backend is the same Phoenix app that mounts Tango, skip HTTP entirely and call Tango.create_session/3, Tango.authorize_url/2, and Tango.exchange_code/4 directly (see USAGE.md).
Connection Management
Use Tango's programmatic API in your Phoenix application to manage connections:
# List connections for a user
connections = Tango.list_connections(user_id)
# Get connection for API calls
{:ok, connection} = Tango.get_connection_for_provider("github", user_id)
headers = [{"Authorization", "Bearer #{connection.access_token}"}]
# With auto-refresh, a refresh that cannot fall back to a still-valid active
# token returns {:error, {:refresh_failed, reason}}. `reason` is a
# Tango.RefreshError term. Explicit Tango.refresh_connection/1 returns
# {:error, reason} with no wrapper. See README "Upgrading to 0.3.0".
{:ok, connection} = Tango.get_connection_for_provider("github", user_id, auto_refresh: true)
# Revoke by struct, or by integer id. A mismatched struct tenant is
# {:error, :not_authorized}. An unknown id is {:error, :not_found}.
{:ok, _revoked} = Tango.revoke_connection(connection, user_id)
{:ok, _revoked} = Tango.revoke_connection(connection.id, user_id)