Skip to content
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

Open
ndrean opened this issue Dec 20, 2024 · 2 comments
Open

Authentication - Authorisation #223

ndrean opened this issue Dec 20, 2024 · 2 comments

Comments

@ndrean
Copy link
Contributor

ndrean commented Dec 20, 2024

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......

  • for an API, for Liveview, for Phoenix, for channels
  • usingJWT/Oauth/Authenticator/OTP./Passkey (for the devices that can use it..... )???

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


[Screenshot 2024-12-20 at 15 45 05](https://hexdocs.pm/phoenix/mix_phx_gen_auth.html)

There is also 2FA using an authenticator.


Screenshot 2024-12-20 at 15 46 55


And U2F


Screenshot 2024-12-20 at 15 48 54


A comparison between One Time Password and U2F: (same wiki source as above


Screenshot 2024-12-20 at 15 49 38


I saw an "old" post of this:

Screenshot 2024-12-20 at 15 50 41


What about OAthu? Christian Alexander is one of my favorite guy on YT.


Screenshot 2024-12-20 at 15 52 38


Passkey with SimpleWebAuthn
Screenshot 2024-12-20 at 16 40 23


or the WIP:
Screenshot 2024-12-20 at 16 43 16

@ndrean
Copy link
Contributor Author

ndrean commented Dec 20, 2024

Authentication

Core Authentication

  • phx.gen.auth: The recommended starting point for authentication in Phoenix applications. It generates a solid foundation for email/password authentication2[5(https://blog.appsignal.com/2022/01/25/securing-your-phoenix-liveview-apps.html).

  • LiveView-specific authentication: With Phoenix 1.7+, phx.gen.auth can generate LiveView-specific authentication systems (https://fly.io/phoenix-files/phx-gen-auth/).

  • hashed password stored in the database, with "Forgot Password" and email verification flows.
    ####Multi-Factor Authentication (MFA)

  • magic link: a one-time token sent it via email and stored in the database, and validate it when clicked and create a session.

  • Time-based One-Time Password (TOTP): Implement TOTP for an additional layer of security (https://elixirforum.com/t/how-do-you-build-authentication-in-your-phoenix-project-in-2024/65104).

  • U2F/WebAuthn: For the strongest multi-factor authentication, implement U2F or WebAuthn support(https://peterullrich.com/uf2-with-phoenix-live-view).

Passwordless Options

Third-Party Authentication

  • OAuth/OpenID Connect: Use libraries like Assent to integrate third-party authentication providers (e.g., GitHub, Google, FB...).
    For example, ueberauth

Authorization

For LiveView

For API

  • JWT (JSON Web Tokens): Use JWTs for stateless authentication in API requests.
  • OAuth Implement OAuth for secure API access, especially when dealing with third-party integrations.

For Channels

  • Token-based Authentication: Use tokens (e.g., JWTs) for authenticating WebSocket connections.
  • Channel Join Authentication: Implement authentication checks in the join callback of your channel.

Security Considerations

  • mix sobelow
  • Leverage Phoenix's built-in CSRF protection (forms & AJAX requests)
  • Session Management: Use Phoenix.Token for creating and validating session tokens (https://peterullrich.com/uf2-with-phoenix-live-view).
  • Rate Limiting: Implement rate limiting to prevent brute-force attacks.

A kinda summary:

Screenshot 2024-12-20 at 17 18 25

@ndrean
Copy link
Contributor Author

ndrean commented Dec 20, 2024

I asked my new best friend Claude:

Phoenix Authentication & Authorization Guide

Core Authentication Concepts

Session-based Authentication (LiveView & Web)

  • Primary authentication for LiveView applications
  • Uses Phoenix's built-in session management
  • Secure default with signed, encrypted cookies
# config/config.exs
config :my_app, MyAppWeb.Endpoint,
  secret_key_base: "long-secret-key",
  live_view: [signing_salt: "another-long-secret"]

API Authentication

  • JWT for stateless API authentication
  • OAuth2 for third-party integration
  • API keys for service-to-service communication

Implementation Patterns

1. Password Authentication

defmodule 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 Authentication

defmodule 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) Support

defmodule 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 Pipeline

defmodule 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 Authentication

defmodule 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

  1. Password Storage

    • Use Bcrypt with appropriate cost factor
    • Never store plain passwords
    • Implement password complexity requirements
  2. Session Management

    • Use secure, HTTP-only cookies
    • Implement session timeout
    • Provide session listing and revocation
  3. Rate Limiting

    • Implement rate limiting for login attempts
    • Use exponential backoff for failed attempts
    • Consider IP-based and account-based limits
  4. Multi-Factor Authentication

    • Offer multiple 2FA options (TOTP, SMS, Email)
    • Allow backup codes for recovery
    • Enforce 2FA for sensitive operations
  5. API Security

    • Use short-lived JWTs
    • Implement refresh token rotation
    • Consider API key rotation policies

Configuration Examples

Guardian (JWT) Configuration

config :my_app, MyApp.Guardian,
  issuer: "my_app",
  secret_key: "your-secret-key",
  ttl: {60, :minute}

Pow (Authentication Framework) Configuration

config :my_app, :pow,
  user: MyApp.Users.User,
  repo: MyApp.Repo,
  web_module: MyAppWeb,
  extensions: [PowPersistentSession, PowResetPassword],
  controller_callbacks: Pow.Extension.Phoenix.ControllerCallbacks

Implementation Recommendations

  1. Start Simple

    • Begin with password-based auth
    • Add MFA as needed
    • Gradually introduce additional methods
  2. Progressive Enhancement

    • Implement Passkeys with password fallback
    • Support multiple auth methods per user
    • Allow method switching based on context
  3. Error Handling

    • Provide clear error messages
    • Log security events
    • Implement account recovery flows
  4. Testing

    • Write tests for all auth flows
    • Test edge cases and failure modes
    • Include security-focused tests

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant