-
Notifications
You must be signed in to change notification settings - Fork 109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Authentication - Authorisation #223
Comments
AuthenticationCore Authentication
Passwordless Options
Third-Party Authentication
Authorization
For LiveView
For API
For Channels
Security Considerations
A kinda summary: |
I asked my new best friend Claude: Phoenix Authentication & Authorization GuideCore Authentication ConceptsSession-based Authentication (LiveView & Web)
# config/config.exs
config :my_app, MyAppWeb.Endpoint,
secret_key_base: "long-secret-key",
live_view: [signing_salt: "another-long-secret"] API Authentication
Implementation Patterns1. Password Authenticationdefmodule MyApp.Accounts do
import Ecto.Query
alias MyApp.Repo
alias MyApp.Accounts.User
def authenticate_user(email, password) do
User
|> Repo.get_by(email: email)
|> case do
nil -> {:error, :not_found}
user -> verify_password(user, password)
end
end
defp verify_password(user, password) do
if Bcrypt.verify_pass(password, user.password_hash) do
{:ok, user}
else
{:error, :invalid_password}
end
end
end 2. Magic Link Authenticationdefmodule MyApp.Accounts.MagicLinks do
def create_magic_link(email) do
user = Repo.get_by(User, email: email)
token = generate_token()
# Store token with expiry
Repo.insert!(%MagicLink{
user_id: user.id,
token: token,
expires_at: DateTime.utc_now() |> DateTime.add(30 * 60, :second)
})
# Send email with link
MyApp.Mailer.deliver_magic_link(email, token)
end
def verify_magic_link(token) do
MagicLink
|> where([l], l.token == ^token)
|> where([l], l.expires_at > ^DateTime.utc_now())
|> Repo.one()
|> case do
nil -> {:error, :invalid_or_expired}
link -> {:ok, Repo.get(User, link.user_id)}
end
end
end 3. TOTP (Time-based One-Time Password)defmodule MyApp.Accounts.TOTP do
def setup_totp(user) do
secret = NimbleTOTP.secret()
uri = NimbleTOTP.otpauth_uri("MyApp:#{user.email}", secret)
# Store encrypted secret
Repo.update!(User.totp_changeset(user, %{
totp_secret: encrypt_secret(secret)
}))
# Return URI for QR code generation
uri
end
def verify_totp(user, code) do
case NimbleTOTP.valid?(decrypt_secret(user.totp_secret), code) do
true -> {:ok, user}
false -> {:error, :invalid_code}
end
end
end 4. Passkey (WebAuthn) Supportdefmodule MyApp.Accounts.Passkey do
def start_registration(user) do
# Generate challenge
challenge = :crypto.strong_rand_bytes(32)
# Store challenge temporarily
Cachex.put(:passkey_cache, user.id, challenge, ttl: 300_000)
# Return registration options
%{
challenge: Base.encode64(challenge),
rp: %{
name: "MyApp",
id: "myapp.com"
},
user: %{
id: user.id,
name: user.email,
displayName: user.name
},
pubKeyCredParams: [
%{type: "public-key", alg: -7}, # ES256
%{type: "public-key", alg: -257} # RS256
],
timeout: 300000,
attestation: "direct"
}
end
def complete_registration(user, attestation) do
# Verify attestation and store credential
with {:ok, challenge} <- Cachex.get(:passkey_cache, user.id),
{:ok, credential} <- WebAuthn.verify_registration(attestation, challenge) do
Repo.insert!(%Passkey{
user_id: user.id,
credential_id: credential.id,
public_key: credential.public_key
})
end
end
end 5. LiveView Authentication Pipelinedefmodule MyAppWeb.AuthLive do
import Phoenix.LiveView
def on_mount(:default, _params, session, socket) do
case session do
%{"user_token" => token} ->
case MyApp.Accounts.get_user_by_session_token(token) do
nil ->
{:halt, redirect(socket, to: "/login")}
user ->
{:cont, assign(socket, :current_user, user)}
end
_ ->
{:halt, redirect(socket, to: "/login")}
end
end
end 6. Channel Authenticationdefmodule MyAppWeb.UserSocket do
use Phoenix.Socket
def connect(%{"token" => token}, socket, _connect_info) do
case MyApp.Accounts.verify_token(token) do
{:ok, user_id} ->
{:ok, assign(socket, :user_id, user_id)}
{:error, _} ->
:error
end
end
def connect(_params, _socket, _connect_info), do: :error
end Security Best Practices
Configuration ExamplesGuardian (JWT) Configurationconfig :my_app, MyApp.Guardian,
issuer: "my_app",
secret_key: "your-secret-key",
ttl: {60, :minute} Pow (Authentication Framework) Configurationconfig :my_app, :pow,
user: MyApp.Users.User,
repo: MyApp.Repo,
web_module: MyAppWeb,
extensions: [PowPersistentSession, PowResetPassword],
controller_callbacks: Pow.Extension.Phoenix.ControllerCallbacks Implementation Recommendations
|
Does someone has a clear view of what to do, best practice? Or am I throwing a bottle in the ocean...
The ocean is: Passwords, magic link, OPT, Authenticator, Passkey, OAuth, third-party......
In particular, I understand the Phoenix generates a session token when a user connects. This token is accessible "on mount" (the HTTP call) by the LiveView and put into the socket
[](https://hexdocs.pm/phoenix/mix_phx_gen_auth.html)
There is also 2FA using an authenticator.
And U2F
A comparison between One Time Password and U2F: (same wiki source as above
I saw an "old" post of this:
What about OAthu? Christian Alexander is one of my favorite guy on YT.
Passkey with SimpleWebAuthn
or the WIP:
The text was updated successfully, but these errors were encountered: