Skip to main content

lib/mobile_id_token.ex

defmodule MobileIdToken do
  @moduledoc """
  Verifies mobile OAuth `id_token` JWTs issued by Apple and Google.

  `verify/3` is the provider-agnostic entrypoint. It delegates to
  `MobileIdToken.Apple` and `MobileIdToken.Google`.
  """

  alias MobileIdToken.{Apple, Google}

  @type provider :: :apple | :google
  @typedoc """
  Verification options.

  - `:client_ids` - accepted `aud` values (list, comma-separated string, or single string)
  - `:nonce` - expected nonce (Apple expects this to be present; Google allows `nil`)

  The library does not read host app env vars directly; pass resolved client IDs explicitly.
  """
  @type verify_opts :: [client_ids: [String.t()] | String.t(), nonce: String.t() | nil]

  @type verify_error ::
          :invalid_token
          | :missing_kid
          | :jwk_not_found
          | :invalid_signature
          | :invalid_issuer
          | :missing_client_id
          | :invalid_audience
          | :token_expired
          | :invalid_claims
          | :email_not_verified
          | :invalid_nonce
          | :jwks_unavailable
          | :unsupported_provider

  @doc """
  Verifies an OAuth `id_token` for the given provider.

  ## Examples

      iex> MobileIdToken.verify(:google, token, client_ids: ["my-client-id"])
      {:ok, claims}

      iex> MobileIdToken.verify(:apple, token, client_ids: ["com.example.app"], nonce: "abc123")
      {:ok, claims}
  """
  @spec verify(provider(), String.t(), verify_opts()) :: {:ok, map()} | {:error, verify_error()}
  def verify(provider, id_token, opts \\ [])

  def verify(:apple, id_token, opts), do: Apple.verify_id_token(id_token, opts)
  def verify(:google, id_token, opts), do: Google.verify_id_token(id_token, opts)
  def verify(_provider, _id_token, _opts), do: {:error, :unsupported_provider}
end