Skip to content

An example React + Vite frontend with Node.js backend that uses FusionAuth as the IdP for OpenID Connect (OIDC) authentication.

Notifications You must be signed in to change notification settings

ThisIsRuvos/aims-auth-example

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AIMS Authentication Example

A proof-of-concept TypeScript application demonstrating FusionAuth OIDC authentication using the authorization code flow with httpOnly session cookies.

Prerequisites

  • Docker (docker compose)
  • If connecting to AIMS FusionAuth SaaS instance (not local FusionAuth), FusionAuth URL and client ID/secret

Start FustionAuth and the Example Application Locally

  1. Clone this project
  2. Create /fusionauth/.env from the contents of /fusionauth/.env.local, e.g., cp fusionauth/.env.local fusionauth/.env
  3. Build/start the Docker Compose stack consisting of FusionAuth and the AIMS Authentication Example app.
    docker compose up
  4. Access the app at http://localhost:3000.

Start the Example Application Locally and Connect with AIMS FusionAuth SaaS

  1. Follow the same local start steps above, but replace the FUSIONAUTH_ISSUER, FUSIONAUTH_CLIENT_ID, and FUSIONAUTH_CLIENT_SECRET values in docker-compose.yml with values provided from the AIMS team.

Architecture

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│   React + Vite  │────▶│   Express.js    │────▶│   FusionAuth    │
│    Frontend     │◀────│    Backend      │◀────│      IdP        │
└─────────────────┘     └─────────────────┘     └─────────────────┘
  • Frontend: React + Vite + React Router with protected routes
  • Backend: Express.js serves frontend app and handles authentication
  • Auth: FusionAuth as the OIDC IdP

FusionAuth Concepts

Core Terminology Mapping

Other IdPs FusionAuth Notes
Realm / Organization Tenant Isolated user pools with separate config. This project creates a dev tenant.
OAuth Client / App Registration Application Where you configure OAuth settings, redirect URIs, grants, etc.
User Assignment / App Access Registration The explicit link between a User and an Application—users must be registered to an app to authenticate.
Rules / Actions / Mappers Lambdas JavaScript functions to customize tokens, reconcile external IdP users, etc.
Custom Attributes / User Metadata user.data or registration.data Unstructured JSON. Registration data is app-scoped; user data is global.

Key Concepts

Tenants FusionAuth is multi-tenant by design. Each tenant has completely isolated users, applications, and configuration. Cross-tenant SSO is possible but opt-in. In this example, we create a separate dev tenant rather than using the default tenant—a best practice to avoid polluting the FusionAuth admin space.

Applications vs Registrations This is the biggest conceptual difference from most IdPs:

  • An Application is the OAuth client configuration (client ID, secret, redirect URIs)
  • A Registration is a user's membership in that application, including app-specific roles and custom data

Users without a registration for your application will be rejected at login (when requireRegistration: true). This gives you fine-grained control; a user can exist in FusionAuth but not have access to your specific app.

User (global identity)
  └── Registration (per-application)
        ├── roles: ["admin", "editor"]
        └── data: { jurisdiction_id: "CA" }

Lambdas (Token Customization) Lambdas are JavaScript functions that run at specific points in the auth lifecycle. This project uses a JWTPopulate lambda to add a custom claim:

function populate(jwt, user, registration) {
  jwt.jurisdiction_id = registration.data.jurisdiction_id;
}

Kickstart (Infrastructure as Code) Kickstart is FusionAuth's declarative provisioning system. The kickstart.json file in this project bootstraps:

  • API keys
  • Tenants
  • Applications with OAuth configuration
  • Custom form fields
  • Lambdas
  • Test users with registrations

This runs automatically on first startup when FUSIONAUTH_APP_KICKSTART_FILE is set. Think of it as a migration that only runs once on an empty database.

Use docker compose down -v to clear the FusionAuth volumes and wipe all of the Kickstart configuration.

Implementation Notes

OIDC Discovery FusionAuth fully supports OIDC discovery at /.well-known/openid-configuration. This project uses the openid-client library which auto-configures from the issuer URL—no manual endpoint configuration needed.

Internal vs External Issuer URLs When running in Docker, the backend talks to FusionAuth via the internal Docker network (http://fusionauth:9011), but redirect URLs sent to the browser must use the external URL (http://localhost:9011). See FUSIONAUTH_ISSUER vs FUSIONAUTH_INTERNAL_ISSUER in the config.

Session vs Token Storage This implementation stores user claims in a server-side session (httpOnly cookie) rather than exposing tokens to the client. The access token could alternatively be stored for API-to-API calls or refresh token rotation.

Self-Registration Self-registration is disabled in this example (registrationConfiguration.enabled: false). Users must be pre-created with registrations via the Admin UI, API, or Kickstart. Enable this for consumer-facing apps where users sign themselves up.

Admin UI Quick Reference

FusionAuth's admin UI is available at your issuer URL (default: http://localhost:9011). Key locations:

  • Applications: Settings → Applications (OAuth config, lambdas, JWT settings)
  • Users: Users → [search] → Manage (see registrations per user)
  • Registrations: Users → [user] → Registrations tab (app-specific data/roles)
  • Lambdas: Customizations → Lambdas
  • API Keys: Settings → API Keys
  • Themes: Customizations → Themes (login page styling)

FusionAuth Client Libraries

This project uses standard OIDC (openid-client) rather than FusionAuth-specific SDKs. FusionAuth also provides:

  • @fusionauth/typescript-client: Full API client for user management, admin operations
  • @fusionauth/react-sdk: React hooks for client-side auth (alternative approach)

The OIDC approach shown here is portable and recommended when you only need authentication, not user management.

Features

  • Authorization code flow
  • Secure httpOnly session cookies (auth-session)
  • Protected client-side routes with React Router
  • State and nonce verification for CSRF protection
  • Automatic session management

API Routes

Route Method Description
/api/login GET Redirects to FusionAuth login
/api/auth/callback GET OIDC callback - exchanges code for tokens, creates session
/api/logout GET Clears session and redirects to FusionAuth logout
/api/user GET Returns current user object or null

User Management

Startup User Creation

The ./fusionauth/kickstart/kickstart.json file sets up 5 users on the initial start up.

Adding Users via Script

Since self-registration is disabled, users must be created via the Admin UI, API, or the provided script.

Due to licensing limitations for local development, it is easiest to create additional users via the ./scripts/add-user.sh script as detailed below.

Using the add-user script:

# Make script executable (first time only)
chmod +x scripts/add-user.sh

# Interactive mode (prompts for all fields)
./scripts/add-user.sh

# Or provide all arguments at once
./scripts/add-user.sh email@example.com username "Full Name" password CA

# With custom user ID (optional)
./scripts/add-user.sh email@example.com username "Full Name" password CA <user-id>

Required environment variables (use defaults from script or export):

  • FUSIONAUTH_API_KEY
  • FUSIONAUTH_ISSUER
  • FUSIONAUTH_TENANT_ID
  • FUSIONAUTH_APPLICATION_ID

The script will:

  1. Create a user in FusionAuth
  2. Register the user to the application with the specified jurisdiction_id
  3. Make the user active for authentication

Project Structure

aims-auth-example/
├── backend/
│   ├── src/
│   │   ├── index.ts           # Express server entry point
│   │   ├── config.ts          # Environment configuration
│   │   ├── routes/
│   │   │   └── auth.ts        # Authentication routes
│   │   └── middleware/
│   │       └── session.ts     # Session middleware
│   ├── package.json
│   └── tsconfig.json
├── frontend/
│   ├── src/
│   │   ├── main.tsx           # React entry point
│   │   ├── App.tsx            # Router configuration
│   │   ├── pages/
│   │   │   ├── Landing.tsx    # Public landing page
│   │   │   └── Dashboard.tsx  # Protected dashboard
│   │   ├── components/
│   │   │   └── ProtectedRoute.tsx
│   │   └── hooks/
│   │       └── useAuth.ts     # Authentication hook
│   ├── index.html
│   ├── package.json
│   └── vite.config.ts
├── scripts/
│   └── add-user.sh      # Script to add new FusionAuth users
└── docker-compose.yml   # Compose file for spinning up the project
└── Dockerfile           # Build instructions for the example application
└── README.md

About

An example React + Vite frontend with Node.js backend that uses FusionAuth as the IdP for OpenID Connect (OIDC) authentication.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •