KeenAuth.Plug.AuthSession (KeenAuth v1.0.1)

Copy Markdown View Source

A separate session cookie for OAuth authentication flow.

This plug creates a dedicated short-lived session cookie for storing OAuth state (nonce, PKCE verifier, redirect URL) during the authentication flow. This provides several security benefits:

Why Dual Cookies?

OAuth providers like Microsoft Entra can use form_post response mode (POST callback) which is more secure than query mode (tokens don't appear in URL/browser history). However, SameSite=Lax cookies are NOT sent on cross-site POST requests.

The solution is two cookies:

  • Auth cookie: SameSite=None; Secure - sent on cross-site POSTs, short-lived (5 min)
  • Main cookie: SameSite=Lax or Strict - not sent cross-site, long-lived

Security Benefits

  • Session fixation prevention: New session ID after authentication
  • Minimal exposure window: Auth cookie expires in 5 minutes
  • Token security: Allows form_post mode (tokens not in URL)
  • Cookie isolation: Auth state separate from user session

Usage

Add to your router's authentication pipeline:

pipeline :authentication do
  plug KeenAuth.Plug, otp_app: :my_app
  plug KeenAuth.Plug.AuthSession
end

Configuration

Configure in your endpoint or via plug options:

plug KeenAuth.Plug.AuthSession,
  key: "_my_app_auth",
  signing_salt: "auth_signing_salt",
  encryption_salt: "auth_encryption_salt",
  same_site: "None",  # Required for cross-site POST
  secure: true,       # Required with SameSite=None
  max_age: 300        # 5 minutes

How It Works

  1. OAuth /new → State stored in auth cookie (SameSite=None)
  2. Provider redirects with POST → Auth cookie sent (cross-site OK)
  3. OAuth /callback → State validated and cleared
  4. Success → User stored in main session, auth cookie deleted

Summary

Functions

Clear the entire auth session.

Clear auth session and regenerate the main session ID.

Delete a value from the auth session.

Get a value from the auth session.

Get a value from auth session and delete it atomically.

Put a value in the auth session.

Regenerate main session ID without clearing auth data.

Functions

clear(conn)

@spec clear(Plug.Conn.t()) :: Plug.Conn.t()

Clear the entire auth session.

clear_and_regenerate(conn)

@spec clear_and_regenerate(Plug.Conn.t()) :: Plug.Conn.t()

Clear auth session and regenerate the main session ID.

Call this after successful authentication to:

  1. Clear OAuth flow data from auth cookie
  2. Regenerate main session ID (prevents session fixation)

delete(conn, key)

@spec delete(Plug.Conn.t(), atom()) :: Plug.Conn.t()

Delete a value from the auth session.

get(conn, key)

@spec get(Plug.Conn.t(), atom()) :: any()

Get a value from the auth session.

get_and_delete(conn, key)

@spec get_and_delete(Plug.Conn.t(), atom()) :: {Plug.Conn.t(), any()}

Get a value from auth session and delete it atomically.

put(conn, key, value)

@spec put(Plug.Conn.t(), atom(), any()) :: Plug.Conn.t()

Put a value in the auth session.

regenerate(conn)

@spec regenerate(Plug.Conn.t()) :: Plug.Conn.t()

Regenerate main session ID without clearing auth data.