🚀
ShipClojure
  • README
  • Development
    • Getting Started
    • REPL Workflow
    • AI Development with ShipClojure
    • Getting Updates
    • Formatting code
    • ShipClojure Guiding Principles
  • Backend
    • Migrations
    • Secrets
    • Routing
    • ShipClojure Blog
    • Email
  • Frontend
    • UIx + re-frame
    • HTTP Requests with Re-frame
    • Frontend Navigation with Re-frame
    • Toast Notifications
    • Icons
  • Server Side Rendering
    • Static/Landing pages
  • Auth
    • How Auth works
    • Oauth2 providers
  • Deployment
    • Deployment
  • Decisions
    • 001 - Cookie Sessions
    • 002 - Single Page Application Architecture
    • 003 - Re-frame instead of Refx
    • 003 - Move from cookie sessions to JWT Access + refresh tokens
Powered by GitBook
On this page
  • Overview: Refresh Token + Access Token Approach
  • Authentication Flow
  • Key Files
  • Client-side (ClojureScript)
  • Server-side (Clojure)
  • Protecting API Routes
  • Accessing User Information in Route Handlers
  • Refresh Token Persistence
  1. Auth

How Auth works

This document explains how authentication works in the application.

Overview: Refresh Token + Access Token Approach

Our authentication system uses two types of tokens:

  1. Access Token: Short-lived token (valid for 15 minutes) used to authenticate users for protected API routes. These tokens are kept only in memory and never stored in persistent storage.

  2. Refresh Token: Long-lived token (valid for 90 days) used only to obtain a new pair of access and refresh tokens when the access token expires. This token is stored in browser's local storage.

This approach follows security best practices by minimizing the exposure of the more powerful access token while providing seamless user experience.

Authentication Flow

  1. Initial Login:

    • User logs in with credentials (email/password) or via OAuth provider

    • Server validates credentials and creates a token pair

    • Access token is kept in memory (JavaScript variable)

    • Refresh token is stored in local storage

  2. Authenticated Requests:

    • For each API request, the access token is attached in the Authorization header

    • Server validates the token and processes the request

  3. Token Refresh Flow:

    • When access token expires (or is about to expire), client uses the refresh token to get a new token pair

    • If refresh is successful, new access token is kept in memory and new refresh token replaces the old one in local storage

    • This creates a persistent login experience across page refreshes

  4. Token Rotation:

    • For security, we implement refresh token rotation - each time a refresh token is used, it's replaced with a new one

    • This helps prevent token replay attacks

    • If a refresh token is used more than once (potentially indicating it was leaked), the system invalidates the entire token family

Key Files

Client-side (ClojureScript)

  • src/cljs/saas/auth.cljs: Contains the core authentication logic for the front-end including:

    • Token management

    • Login/logout functions

    • Token refresh logic

    • OAuth authentication

Server-side (Clojure)

  • src/clj/saas/web/controllers/auth/handlers.clj: Contains server-side authentication handlers:

    • User registration

    • Login

    • Token generation

    • Refresh token rotation logic

    • OAuth providers integration

Protecting API Routes

API routes that require authentication are protected using middleware:

;; Example from src/clj/saas/web/routes/api.clj
["" {:middleware [[mw/wrap-jwt-auth]]}
  ;; Public routes that only need JWT parsing but don't require authentication
  
  ["/me" {:middleware [[mw/wrap-authenticated]]}
   ;; Protected routes that require valid authentication
   ]]

The application uses two middleware functions:

  1. wrap-jwt-auth: Decodes the JWT token and adds :claims to the request object

  2. wrap-authenticated: Ensures that a user is authenticated by checking for the existence of :sub in claims

Accessing User Information in Route Handlers

When handling requests for authenticated routes, you can access the user information from the token claims:

(defn get-user-data [req]
  (let [claims (:claims req)
        user-id (:sub claims)]
    ;; Access other claims like :email, :role, etc.
    ;; Process request with user context
    ))

Common claims available in the token:

  • :sub: The subject (user ID)

  • :exp: Expiration time

  • :iat: Issued at time

  • Other custom claims depending on your application

Refresh Token Persistence

Login persistence works because:

  1. On page load/refresh, the system checks for a refresh token in local storage

  2. If found, it uses this token to request a new token pair from the server

  3. This happens automatically before the user interacts with the application

  4. As long as the refresh token is valid, the user remains logged in without having to re-enter credentials

This approach balances security (access tokens are ephemeral and never stored) with user experience (seamless login persistence through refresh tokens).

PreviousStatic/Landing pagesNextOauth2 providers

Last updated 1 month ago

For more detailed information on refresh token rotation, see the .

Auth0 documentation