AshAuthentication.Firebase

Elixir CIHex.pmHex.pm

Firebase token authentication strategy for AshAuthentication.

Requirements

Installation

The package can be installed by adding ash_authentication_firebase to your list of dependencies in mix.exs:

def deps do
[
{:ash_authentication_firebase, "~> 1.0"}
]
end

Usage

Please consult the official AshAuthentication docs for how to scaffold a resource. Below is a complete minimal example showing the parts this strategy requires.

Add AshAuthentication.Strategy.Firebase to your resource extensions, declare an identity keyed on the Firebase user id, and define a create action with upsert?: true / upsert_identity: so repeat sign-ins update the existing user. The strategy validates this shape at compile time and raises a Spark.Error.DslError if anything is missing.

defmodule MyApp.Accounts.User do
use Ash.Resource,
domain: MyApp.Accounts,
data_layer: AshPostgres.DataLayer,
extensions: [AshAuthentication, AshAuthentication.Strategy.Firebase]
attributes do
uuid_primary_key :id
attribute :uid, :string, allow_nil?: false, public?: true
attribute :email, :string, public?: true
end
identities do
identity :unique_uid, [:uid]
end
actions do
defaults [:read]
create :register_with_firebase do
argument :user_info, :map, allow_nil?: false
upsert? true
upsert_identity :unique_uid
change fn changeset, _ ->
info = Ash.Changeset.get_argument(changeset, :user_info)
changeset
|> Ash.Changeset.change_attribute(:uid, info["uid"])
|> Ash.Changeset.change_attribute(:email, info["email"])
end
end
end
authentication do
strategies do
# Multiple firebase strategies with different project_ids are supported.
firebase :firebase do
project_id "project-123abc"
token_input :firebase_token
end
end
end
end

Sign-in only (no auto-registration)

Set registration_enabled?(false) and replace the create action with a read action — useful when users must be provisioned out-of-band before they can sign in.

actions do
defaults [:read]
read :sign_in_with_firebase do
argument :user_info, :map, allow_nil?: false
get? true
prepare fn query, _ ->
uid = Ash.Query.get_argument(query, :user_info)["uid"]
Ash.Query.filter(query, uid == ^uid)
end
end
end
authentication do
strategies do
firebase :firebase do
project_id "project-123abc"
token_input :firebase_token
registration_enabled? false
end
end
end

Secrets and Runtime Configuration

To avoid hardcoding your Firebase project id in your source code, you can use the AshAuthentication.Secret behaviour. This allows you to provide the project id through runtime configuration using either an anonymous function or a module.

Examples:

Using an anonymous function:

authentication do
strategies do
firebase :firebase do
project_id fn _path, _resource ->
Application.fetch_env(:my_app, :firebase_project_id)
end
token_input :firebase_token
end
end
end

Using a module:

defmodule MyApp.Secrets do
use AshAuthentication.Secret
def secret_for([:authentication, :strategies, :firebase, :project_id], MyApp.Accounts.User, _opts, _context) do
Application.fetch_env(:my_app, :firebase_project_id)
end
end
# And in your resource:
authentication do
strategies do
firebase :firebase do
project_id MyApp.Secrets
token_input :firebase_token
end
end
end

Security model

This library performs the token-verification checks documented by Firebase:

What this library does not verify:

Telemetry

The library emits the following :telemetry events. Attach handlers to pipe them into your observability stack of choice (Prometheus, StatsD, etc.).

EventMeasurementsMetadata
[:ash_authentication_firebase, :key_store, :fetched]%{retry_attempt, keys_count, expires_in}%{}
[:ash_authentication_firebase, :key_store, :fetch_failed]%{retry_attempt, delay}%{reason}
[:ash_authentication_firebase, :strategy, :token_rejected]%{count: 1}%{reason, strategy}
[:ash_authentication_firebase, :strategy, :missing_secret]%{count: 1}%{strategy, path}

Acknowledgements

Inspired by ExFirebaseAuth.