Skip to content

Commit

Permalink
feat: better integration with sdk and documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
zoedsoupe committed Sep 21, 2024
1 parent 039a67f commit d58b1f3
Show file tree
Hide file tree
Showing 9 changed files with 697 additions and 357 deletions.
2 changes: 0 additions & 2 deletions .envrc
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,3 @@ export ERL_AFLAGS="-kernel shell_history enabled"
export LANG=en_US.UTF-8

use flake

source .env
247 changes: 237 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,261 @@
# Supabase GoTrue

[Auth](https://supabase.com/docs/guides/auth) implementation for the `supabase_potion` SDK in Elixir.
[Auth](https://supabase.com/docs/guides/auth) implementation for the [Supabase Potion](https://hexdocs.pm/supabase_potion) SDK in Elixir.

## Installation

```elixir
def deps do
[
{:supabase_potion, "~> 0.3"},
{:supabase_gotrue, "~> 0.2"}
{:supabase_potion, "~> 0.5"},
{:supabase_gotrue, "~> 0.3"}
]
end
```

## Usage

Firstly you need to initialize your Supabase client(s) as can be found on the [supabase_potion documentation](https://hexdocs.pm/supabase_potion/Supabase.html#module-starting-a-client):
Firstly you need to initialize your Supabase client(s) as can be found on the [Supabase Potion documentation](https://hexdocs.pm/supabase_potion/readme.html#usage).

Now you can pass the Client to the `Supabase.GoTrue` functions:

```elixir
iex> Supabase.init_client(%{name: Conn, conn: %{base_url: "<supa-url>", api_key: "<supa-key>"}})
{:ok, #PID<>}
iex> Supabase.GoTrue.sign_in_with_password(client, %{} = params)
```

Now you can pass the Client to the `Supabase.GoTrue` functions as a `PID` or the name that was registered on the client initialization:
> Note that this example consider that you already have a `client` variable with the Supabase client.
> Note that this example consider that you al already configured the `Supabase.GoTrue` module in your configuration file. As mentioned in the [next section](#configuration).
This implementation also exposes an `Supabase.GoTrue.Admin` function to interact with users with super powers:
```elixir
iex> Supabase.GoTrue.sign_in_with_password(pid | client_name, %{} = params)
iex> Supabase.GoTrue.Admin.create_user(client, %{} = params)
```

This implementation also exposes an `Supabase.GoTrue.Admin` function to interact with users with super powers:
### Examples

There are sample apps in the `examples` directory that demonstrate how to use the `Supabase.GoTrue` module in your application.

Check the [Supabase Potion examples showcase](https://github.com/zoedsoupe/supabase-ex?tab=readme-ov-file#examples)!

### Configuration

You can configure the `Supabase.GoTrue` module in your `config.exs` file:

```elixir
import Config

config :supabase_gotrue, auth_module: MyAppWeb.Auth
```

### Available authentication methods
- [Sign in with ID Token](https://hexdocs.pm/supabase_gotrue/Supabase.GoTrue.html#sign_in_with_id_token/2)
- [Sign in with email and password](https://hexdocs.pm/supabase_gotrue/Supabase.GoTrue.html#sign_in_with_password/2)
- [Sign in with Oauth](https://hexdocs.pm/supabase_gotrue/Supabase.GoTrue.html#sign_in_with_oauth/2)
- [Sign in with OTP](https://hexdocs.pm/supabase_gotrue/Supabase.GoTrue.html#sign_in_with_otp/2)
- [Sign in with SSO](https://hexdocs.pm/supabase_gotrue/Supabase.GoTrue.html#sign_in_with_sso/2)
- [Anonymous Sign in](https://hexdocs.pm/supabase_gotrue/Supabase.GoTrue.html#sign_in_anonymously/1)
- [Sign up with email and password](https://hexdocs.pm/supabase_gotrue/Supabase.GoTrue.html#sign_up/2)

### Plug based applications (or Phoenix "dead views")

`Supabase.GoTrue.Plug` provides Plug-based authentication support for the `Supabase GoTrue` authentication in Elixir applications.

The module offers a series of functions to manage user authentication through HTTP requests in Phoenix applications with **"dead views"** or plain Plug based application. It facilitates operations like signing-in, signing-out, fetch the current user, and more.

To use the `Supabase.GoTrue.Plug` module, you need first to define a module that will handle the authentication in your application:

```elixir
defmodule MyAppWeb.Auth do
use Supabase.GoTrue.Plug,
client: MyApp.Supabase.Client,
endpoint: MyAppWeb.Endpoint, # required if using Phoenix based applications
signed_in_path: "/app", # required
not_authenticated_path: "/login", # required
session_cookie: "my_app_session", # optional
# optional
session_cookie_options: [
http_only: true,
secure: true,
same_site: :lax,
max_age: 86_400
]
end
```

> [!WARNING]
> The `client` options must be a module that implements the `Supabase.Client.Behaviour` behaviour.
> It should be a [Self Managed Client](https://github.com/zoedsoupe/supabase-ex?tab=readme-ov-file#self-managed-clients) but it can be a [One off Client](https://github.com/zoedsoupe/supabase-ex?tab=readme-ov-file#one-off-clients) if you correctly manage the client state on your application.
So, considering that you have something like this on your `config.exs`:

```elixir
config :my_app, MyApp.Supabase.Client,
base_url: "https://myapp.supabase.co",
api_key: "myapp-api-key"
```

And you have already defined your self managed client module:

```elixir
# lib/my_app/supabase/client.ex
defmodule MyApp.Supabase.Client do
use Supabase.Client, otp_app: :my_app
end
```

Then you can use the `Supabase.GoTrue.Plug` module! The module define a series of plugs that you can use in your router:

```elixir
import MyAppWeb.Auth

plug :fetch_current_user # this plug will fetch the current user and assign it to the `conn.assigns[:current_user]`
plug :redirect_if_user_is_authenticated # this plug will redirect to the `signed_in_path` if the user is authenticated
plug :require_authenticated_user # this plug will redirect to the `not_authenticated_path` if the user is not authenticated
```

For example, in your Phoenix router you can use your defined authentication handler module like this:

```elixir
defmodule MyAppWeb.Router do
use MyAppWeb, :router

import MyAppWeb.Auth

pipeline :browser do
# rest of plugs
plug :fetch_current_user
end

# if a user is already authenticted, redirect to the signed_in_path
# already authenticated users will not be able to access this scope
scope "/", MyAppWeb do
pipe_through [:browser, :redirect_if_user_is_authenticated]

get "/login", LoginController, :show
post "/login", LoginController, :create
end

# if a user is not authenticated, redirect to the not_authenticated_path
# not authenticated users will not be able to access this scope
scope "/app", MyAppWeb do
pipe_through [:browser, :require_authenticated_user]

get "/", AppController, :index
end
end
```

Also, `Supabase.GoTrue.Plug` provides a series of helper functions that you can use in your login/auth controllers, so with your defined module you can use like this:

```elixir
iex> Supabase.GoTrue.Admin.create_user(pid | client_name, %{} = params)
defmodule MyApp.LoginController do
use MyAppWeb, :controller

import MyAppWeb.Auth

def show(conn, _params) do
render(conn, "login.html")
end

def create(conn, %{"email" => email, "password" => password}) do
case log_in_with_password(conn, %{"email" => email, "password" => password}) do
{:ok, updated_conn} ->
# here the `updated_conn` will contain the access token
# and also will redirect to the `signed_in_path`
put_flash(updated_conn, :info, "You have successfully signed in!")

# this clause means that the user provided invalid credentials
# so we will render the login form again with an error message
{:error, _reason} ->
conn
|> put_flash(:error, "Invalid email or password")
|> render("login.html")
end
end
end
```

The `log_in_with_password/2` exposed by the `Supabase.GoTrue.Plug` module is one of the various ways that you can start a session with the `Supabase GoTrue` authentication service.

For more ways to authenticate users, please refer to the [Supabase.GoTrue module documentation](https://hexdocs.pm/supabase_gotrue/Supabase.GoTrue.htm) and the [official Supabase documentation](https://supabase.io/docs/gotrue).

If you're new to the `Plug` library, you can learn more about it in the [official documentation](https://hexdocs.pm/plug).

Also if you're new to the [Phoenix framework](https://phoenixframework.org), you can learn more about it in the [official getting started section](https://hexdocs.pm/phoenix/directory_structure.html).

### Phoenix LiveView applications

Similar to the `Supabase.GoTrue.Plug` module, the `Supabase.GoTrue.LiveView` module provides LiveView-based authentication support for the `Supabase GoTrue` authentication in Elixir applications.

`Supabase.GoTrue.LiveView` defines Server Hooks that you can use in your LiveView modules to manage user authentication through WebSocket connections in Phoenix LiveView applications. These hooks are meant to be used as [on-mount](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#on_mount/1) callbacks in your LiveView modules or [live_session/3](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Router.html#live_session/3) definitions on your router.

To use the `Supabase.GoTrue.LiveView` module, you need first to define a module that will handle the authentication in your application:

```elixir
defmodule MyAppWeb.Auth do
use Supabase.GoTrue.LiveView,
client: MyApp.Supabase.Client, # required
endpoint: MyAppWeb.Endpoint, # required
signed_in_path: "/app", # required
not_authenticated_path: "/login" # required
end
```

> [!WARNING]
> The `client` options must be a module that implements the `Supabase.Client.Behaviour` behaviour.
> It should be a [Self Managed Client](https://github.com/zoedsoupe/supabase-ex?tab=readme-ov-file#self-managed-clients) but it can be a [One off Client](https://github.com/zoedsoupe/supabase-ex?tab=readme-ov-file#one-off-clients) if you correctly manage the client state on your application.
So, considering that you have something like this on your `config.exs`:

```elixir
config :my_app, MyApp.Supabase.Client,
base_url: "https://myapp.supabase.co",
api_key: "myapp-api-key"
```

And you have already defined your self managed client module:

```elixir
# lib/my_app/supabase/client.ex
defmodule MyApp.Supabase.Client do
use Supabase.Client, otp_app: :my_app
end
```

Then in your LiveView module, you can use the module that you defined like this:

```elixir
defmodule MyAppWeb.UserLive do
use MyAppWeb, :live_view

on_mount {MyAppWeb.Auth, :mount_current_user}
on_mount {MyAppWeb.Auth, :ensure_authenticated}

def mount(_params, _session, socket) do
# here you will have the `socket.assigns[:current_user]` available
# and if the user is not authenticated, the user will be redirected to the `not_authenticated_path`
{:ok, socket}
end
end
```

The usage with the `live_session/3` definition is similar. In your router:

```elixir
defmodule MyAppWeb.Router do
use MyAppWeb, :router

scope "/app", MyAppWeb do
live_session :authenticated,
on_mount: [
{MyAppWeb.Auth, :mount_current_user},
{MyAppWeb.Auth, :ensure_authenticated}
] do
live "/user", UserLive
end
end
end
```

If you're new to Phoenix LiveView, you can learn more about it in the [official documentation](https://hexdocs.pm/phoenix_live_view).
10 changes: 10 additions & 0 deletions lib/supabase/go_true.ex
Original file line number Diff line number Diff line change
Expand Up @@ -242,4 +242,14 @@ defmodule Supabase.GoTrue do
end
end
end

@doc """
Retrieves the auth module handle from the application configuration.
Check https://hexdocs.pm/supabase_gotrue/readme.html#usage
"""
def get_auth_module! do

Application.get_env(:supabase_gotrue, :auth_module) ||
raise(Supabase.GoTrue.MissingConfig, key: :auth_module)
end
end
Loading

0 comments on commit d58b1f3

Please sign in to comment.