Current section
Files
Jump to
Current section
Files
lib/fireauth.ex
defmodule Fireauth do
@moduledoc """
Fireauth is a small library for Firebase Auth integration:
- `Fireauth.verify_id_token/2` verifies Firebase SecureToken ID tokens.
- `Fireauth.create_session_cookie/2` mints Firebase session cookies (requires admin service account).
- `Fireauth.Plug` optionally attaches verified claims to `conn.assigns`
and serves Firebase hosted auth helper files at `/__/auth/*` via proxy,
static files, or callback overrides.
"""
alias Fireauth.{Claims, TokenValidator}
alias Fireauth.EmailLinkSender.Result, as: EmailLinkResult
alias Fireauth.ServerAuth.{SignInResult, StartResult}
@type id_token :: String.t()
@type claims :: Claims.t()
defstruct [:user, :claims, :token]
@type t :: %__MODULE__{
user: Fireauth.User.t() | nil,
claims: Fireauth.Claims.t() | nil,
token: String.t() | nil
}
@doc """
Verify a Firebase ID token and return its claims.
Delegates to `Fireauth.TokenValidator` (adapter pattern).
"""
@spec verify_id_token(id_token(), keyword()) :: {:ok, claims()} | {:error, term()}
def verify_id_token(token, opts \\ []) when is_binary(token) and is_list(opts) do
TokenValidator.verify_id_token(token, opts)
end
@doc """
Verify a Firebase session cookie and return its claims.
"""
@spec verify_session_cookie(String.t(), keyword()) :: {:ok, claims()} | {:error, term()}
def verify_session_cookie(cookie, opts \\ []) when is_binary(cookie) and is_list(opts) do
Fireauth.SessionCookieValidator.verify_session_cookie(cookie, opts)
end
@doc """
Exchange an ID token for a Firebase session cookie.
This makes a network call to Google (Identity Toolkit
`projects.createSessionCookie`). It requires a Firebase Admin service account
(OAuth).
"""
@spec create_session_cookie(id_token(), keyword()) ::
{:ok, Fireauth.SessionCookie.session_cookie()} | {:error, term()}
def create_session_cookie(id_token, opts \\ []) when is_binary(id_token) and is_list(opts) do
Fireauth.SessionCookieCreator.create_session_cookie(id_token, opts)
end
@doc """
Build a provider redirect URI for a server-owned OAuth sign-in flow.
"""
@spec start_oauth_sign_in(String.t(), String.t(), keyword()) ::
{:ok, StartResult.t()} | {:error, term()}
def start_oauth_sign_in(provider_id, callback_uri, opts \\ [])
when is_binary(provider_id) and is_binary(callback_uri) and is_list(opts) do
Fireauth.ServerAuth.start_oauth_sign_in(provider_id, callback_uri, opts)
end
@doc """
Finish an OAuth provider callback and return a Firebase ID token.
"""
@spec finish_oauth_sign_in(String.t(), String.t(), String.t() | nil, keyword()) ::
{:ok, SignInResult.t()} | {:error, term()}
def finish_oauth_sign_in(request_uri, session_id, post_body \\ nil, opts \\ [])
when is_binary(request_uri) and is_binary(session_id) and is_list(opts) do
Fireauth.ServerAuth.finish_oauth_sign_in(request_uri, session_id, post_body, opts)
end
@doc """
Send an email-link sign-in action through Firebase Identity Toolkit.
"""
@spec send_email_sign_in_link(String.t(), String.t(), keyword()) ::
{:ok, EmailLinkResult.t()} | {:error, term()}
def send_email_sign_in_link(email, continue_url, opts \\ [])
when is_binary(email) and is_binary(continue_url) and is_list(opts) do
Fireauth.EmailLinkSender.send_sign_in_link(email, continue_url, opts)
end
@doc """
Mint a Firebase custom token for the given UID.
The token is a short-lived JWT (1 hour) signed with the service account
private key. Pass it to the client-side Firebase SDK via
`signInWithCustomToken` to establish a client-side session.
## Options
* `:otp_app` - the OTP app to read config from (default `:fireauth`)
* `:claims` - optional map of additional claims (max 1000 bytes)
"""
@spec create_custom_token(String.t(), keyword()) :: {:ok, String.t()} | {:error, term()}
def create_custom_token(uid, opts \\ []) when is_binary(uid) and is_list(opts) do
Fireauth.CustomToken.create_custom_token(uid, opts)
end
@doc """
Unlink a provider from a Firebase user.
Calls Identity Toolkit `accounts:update` with `deleteProvider`. Requires
an ID token for the user whose provider should be removed.
"""
@spec unlink_provider(String.t(), String.t(), keyword()) :: {:ok, map()} | {:error, term()}
def unlink_provider(id_token, provider_id, opts \\ [])
when is_binary(id_token) and is_binary(provider_id) and is_list(opts) do
Fireauth.Account.unlink_provider(id_token, provider_id, opts)
end
@doc """
Exchange a custom token for a Firebase ID token.
Makes a REST API call to `accounts:signInWithCustomToken`. Useful for
server-side flows that need an ID token for the current user, e.g. to pass
as the `:id_token` option to `finish_oauth_sign_in/4` for provider linking.
"""
@spec exchange_custom_token(String.t(), keyword()) :: {:ok, String.t()} | {:error, term()}
def exchange_custom_token(custom_token, opts \\ [])
when is_binary(custom_token) and is_list(opts) do
Fireauth.CustomToken.exchange_custom_token(custom_token, opts)
end
@doc """
Convert verified Firebase claims into the common user attrs map.
"""
@spec claims_to_user_attrs(claims()) :: Fireauth.User.t()
def claims_to_user_attrs(%Claims{} = claims), do: Fireauth.User.from_claims(claims)
@doc """
Get all identities from a user, claims, or Fireauth struct.
"""
@spec identities(Fireauth.User.t() | Fireauth.Claims.t() | t()) :: map()
def identities(%Fireauth{user: user}), do: identities(user)
def identities(%{identities: identities}) when is_map(identities), do: identities
def identities(_data), do: %{}
@doc """
Check if the given user, claims, or Fireauth struct has an identity for the specified provider.
"""
@spec has_identity?(Fireauth.User.t() | Fireauth.Claims.t() | t(), String.t() | atom()) ::
boolean()
def has_identity?(%Fireauth{user: user}, provider), do: has_identity?(user, provider)
def has_identity?(%{identities: identities}, provider) when is_map(identities) do
Map.has_key?(identities, to_string(provider))
end
def has_identity?(_data, _provider), do: false
@doc """
Get the first identity ID for the given provider from a user, claims, or Fireauth struct.
"""
@spec identity(Fireauth.User.t() | Fireauth.Claims.t() | t(), String.t() | atom()) ::
String.t() | nil
def identity(%Fireauth{user: user}, provider), do: identity(user, provider)
def identity(%{identities: identities}, provider) when is_map(identities) do
case Map.get(identities, to_string(provider)) do
[id | _] -> id
_ -> nil
end
end
def identity(_data, _provider), do: nil
end