Skip to content

Latest commit

 

History

50 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Yeti


demo-authentication

Yeti License

Yeti - The Performance Platform for Agent-Driven Development. Schema-driven APIs, real-time streaming, and vector search. From prompt to production.

A complete authentication and authorization demo. Three auth methods, role-based field masking, and a live React UI -- all in one yeti application.

Demo-authentication shows how yeti's built-in auth system handles Basic auth, JWT tokens, and OAuth providers side-by-side. Admins see every field on an employee record. Viewers see the same endpoint but sensitive fields -- salary, SSN, home address, personal email -- are stripped server-side before the response leaves the wire. No application code required. The schema defines the data, the roles define the permissions, and yeti enforces both automatically.


Why This Demo

Authentication tutorials usually demonstrate one method in isolation. Real applications need all three: service-to-service calls use Basic auth, SPAs use JWT, and end users expect "Sign in with Google." Building that means configuring three separate middleware layers, writing token validation logic, and hand-rolling field-level access control.

This demo collapses all of that into yeti's declarative auth system:

  • Three auth methods, zero application code -- Basic, JWT, and OAuth are declared in Cargo.toml under [package.metadata.auth] and enforced by the yeti-auth extension. No middleware to write, no token parsing, no session management.
  • Attribute-level permissions -- roles define which table fields are visible. The viewer role cannot read salary, ssn, homeAddress, or personalEmail. The server strips these fields before serialization, not after.
  • OAuth role mapping -- provider-based rules assign roles automatically. Google users get admin, GitHub users get viewer. No user provisioning step required.
  • Seed data on startup -- authLoader and dataLoader populate users, roles, and employee records on first boot. The demo is functional immediately.
  • Live React UI -- a single-page app lets you log in with each method, switch between users, and see the RBAC difference in real time.

Quick Start

1. Install

cd ~/yeti/applications
git clone https://github.com/yetirocks/demo-authentication.git

Restart yeti. The frontend builds automatically on first load via npm run build and is served as a static SPA.

2. Log in with Basic auth

curl -s https://localhost:9996/demo-authentication/Employee/?limit=5 \
  -u admin:admin123 | jq

Response (admin -- all fields visible):

[
  {
    "id": "emp-001",
    "name": "Alice Johnson",
    "email": "alice@example.com",
    "department": "Engineering",
    "title": "Senior Developer",
    "salary": 145000,
    "ssn": "123-45-6789",
    "homeAddress": "123 Oak Street, San Francisco, CA",
    "personalEmail": "alice.j@personal.com"
  }
]
curl -s https://localhost:9996/demo-authentication/Employee/?limit=5 \
  -u user:user123 | jq

Response (viewer -- sensitive fields stripped):

[
  {
    "id": "emp-001",
    "name": "Alice Johnson",
    "email": "alice@example.com",
    "department": "Engineering",
    "title": "Senior Developer"
  }
]

The same endpoint, the same data, different fields. The viewer role has attribute_permissions that deny read access to salary, ssn, homeAddress, and personalEmail.

3. Log in with JWT

# Obtain a token pair
curl -s -X POST https://localhost:9996/yeti-auth/login \
  -H "Content-Type: application/json" \
  -d '{"username": "admin", "password": "admin123"}' | jq

Response:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 900
}
# Use the access token
curl -s https://localhost:9996/demo-authentication/Employee/?limit=5 \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." | jq
# Refresh when the access token expires (15 min default)
curl -s -X POST https://localhost:9996/yeti-auth/jwt_refresh \
  -H "Content-Type: application/json" \
  -d '{"refresh_token": "eyJhbGciOiJIUzI1NiIs..."}' | jq

Response:

{
  "access_token": "eyJhbGciOiJIUzI1NiIs...<new>",
  "refresh_token": "eyJhbGciOiJIUzI1NiIs...<new>",
  "token_type": "Bearer",
  "expires_in": 900
}

4. Log in with OAuth

OAuth requires a browser. The flow works like this:

# 1. Redirect the user to the OAuth login endpoint
#    (open in browser -- this initiates the provider redirect)
open "https://localhost:9996/yeti-auth/oauth_login?provider=github&redirect_uri=/demo-authentication/"

# 2. After the provider callback, the user lands back at the app
#    with an authenticated session cookie

# 3. Check the session
curl -s https://localhost:9996/yeti-auth/oauth_user \
  --cookie "yeti_session=<session-cookie>" | jq

Response:

{
  "authenticated": true,
  "provider": "github",
  "user": {
    "login": "octocat",
    "name": "The Octocat",
    "email": "octocat@github.com",
    "avatar_url": "https://avatars.githubusercontent.com/u/..."
  }
}
# 4. Discover available providers and their role mappings
curl -s "https://localhost:9996/yeti-auth/oauth_providers?app_id=demo-authentication" | jq

Response:

{
  "providers": [
    { "name": "github" },
    { "name": "google" }
  ],
  "roles": {
    "github": "viewer",
    "google": "admin"
  }
}

5. Open the web UI

https://localhost:9996/demo-authentication/

The React frontend provides a visual interface for all three auth methods. Log in with Basic or JWT using the seeded credentials, or click an OAuth provider button. After login, click "GET /Employee" to see the role-based field masking in action.


Architecture

Browser / curl / Agent
    |
    +-- Basic auth header ----> yeti-auth (BasicAuthProvider)
    +-- Bearer JWT token -----> yeti-auth (JwtAuthProvider)
    +-- OAuth redirect -------> yeti-auth (OAuthAuthProvider)
          |
          v
    +--------------------------------------------------+
    |              yeti-auth extension                  |
    |  +---------------+  +------------------+         |
    |  |  Credential   |  |  Role            |         |
    |  |  validation   |  |  resolution      |         |
    |  |  (Argon2id)   |  |  (User -> Role)  |         |
    |  +---------------+  +------------------+         |
    |         |                    |                    |
    |         v                    v                    |
    |  +------------------------------------------+    |
    |  |  Attribute-level permission enforcement   |    |
    |  |  (strip fields denied by role)            |    |
    |  +------------------------------------------+    |
    +--------------------------------------------------+
          |
          v
    +--------------------------------------------------+
    |           demo-authentication                     |
    |  +------------+  +---------+  +--------+         |
    |  |  Employee  |  |  User   |  |  Role  |         |
    |  |  (data)    |  |  (auth) |  | (auth) |         |
    |  +------------+  +---------+  +--------+         |
    |                                                  |
    |  React SPA (login UI + RBAC visualization)       |
    +--------------------------------------------------+
          |
          v
    Yeti (embedded RocksDB, yeti-auth extension)

Auth flow: Request arrives -> yeti-auth inspects Authorization header or session cookie -> validates credentials (Argon2id hash check, JWT signature, or OAuth session lookup) -> resolves user to role -> applies attribute_permissions to strip denied fields -> returns filtered response.

Role resolution: Basic/JWT -> User.roleId -> Role table lookup. OAuth -> config rules match provider name to role ID -> Role table lookup.


Features

Basic Authentication

HTTP Basic auth using the Authorization: Basic <base64> header. Credentials are validated against the User table with Argon2id password hashing. A 5-minute credential cache avoids repeated hash computations.

# Admin -- full access
curl -u admin:admin123 https://localhost:9996/demo-authentication/Employee/?limit=5

# Viewer -- restricted fields
curl -u user:user123 https://localhost:9996/demo-authentication/Employee/?limit=5

Seeded users:

Username Password Role Access Level
admin admin123 admin All fields, full CRUD
user user123 viewer Public fields only, read-only

JWT Authentication

JSON Web Token auth using the Authorization: Bearer <token> header. Tokens are issued by POST /yeti-auth/login and contain embedded permissions, so authenticated requests skip the database lookup.

Parameter Value
Algorithm HS256
Access token TTL 900 seconds (15 minutes)
Refresh token TTL 604800 seconds (7 days)
Secret ${JWT_SECRET} environment variable

Token lifecycle:

  1. POST /yeti-auth/login with username and password -- returns access_token + refresh_token
  2. Use access_token in Authorization: Bearer header for API calls
  3. When access_token expires, POST /yeti-auth/jwt_refresh with refresh_token -- returns a new token pair
  4. DELETE /yeti-auth/login to invalidate the session

OAuth Authentication

Browser-based OAuth 2.0 with automatic role assignment. Users are redirected to the provider, authenticated, and redirected back with a session cookie. No user pre-provisioning is needed -- role is determined by provider-based rules under [package.metadata.auth] in Cargo.toml.

Configured providers:

Provider Role Assigned Config Required
Google admin GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET
GitHub viewer GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET

OAuth endpoints:

Endpoint Method Description
/yeti-auth/oauth_login GET Initiate OAuth flow (query: provider, redirect_uri)
/yeti-auth/oauth_callback GET Handle provider redirect (internal)
/yeti-auth/oauth_user GET Check current OAuth session
/yeti-auth/oauth_logout POST Clear OAuth session
/yeti-auth/oauth_refresh POST Refresh provider access token
/yeti-auth/oauth_providers GET List available providers and role mappings

OAuth flow:

  1. Frontend redirects to /yeti-auth/oauth_login?provider=github&redirect_uri=/demo-authentication/
  2. yeti-auth redirects to GitHub's authorization page
  3. User authorizes the application
  4. GitHub redirects to /yeti-auth/oauth_callback with an authorization code
  5. yeti-auth exchanges the code for tokens, resolves role via config rules, sets session cookie
  6. User is redirected to /demo-authentication/ with an active session

Role-Based Field Masking

The core feature this demo illustrates. Two roles access the same Employee table endpoint but receive different fields in the response:

Field Admin Viewer
id visible visible
name visible visible
email visible visible
department visible visible
title visible visible
salary visible denied
ssn visible denied
homeAddress visible denied
personalEmail visible denied

Field masking is enforced server-side via attribute_permissions in the role definition. The denied fields are removed from the serialized response before it is sent -- they never leave the server. No application code is involved.

React Frontend

A single-page application built with React and Vite that provides a visual interface for the demo:

  • Login panel -- switch between Basic auth, JWT, and OAuth login methods
  • Employee data panel -- fetch employee records and see which fields are returned
  • Role indicator -- shows current user, auth method, and assigned role
  • Development mode notice -- warns when auth is bypassed in dev mode

Data Model

Employee Table

The primary data table demonstrating attribute-level access control.

Field Type Required Sensitive Description
id String Yes No Unique employee identifier (e.g., "emp-001")
name String Yes No Full name
email String Yes No Work email address
department String Yes No Department name
title String Yes No Job title
salary Float No Yes Annual salary (admin only)
ssn String No Yes Social security number (admin only)
homeAddress String No Yes Home address (admin only)
personalEmail String No Yes Personal email address (admin only)

User Table (yeti-auth)

Seeded via authLoader on startup.

Field Value Description
username admin Admin user with full CRUD access
password admin123 Hashed with Argon2id on load
roleId admin References the admin role
email admin@example.com User email
username user Standard user with read-only access
password user123 Hashed with Argon2id on load
roleId viewer References the viewer role
email user@example.com User email

Role Table (yeti-auth)

Seeded via authLoader on startup. Defines permissions per database, per table, per field.

Role Employee CRUD Attribute Restrictions
super_user Full (all databases) None
admin Read, Insert, Update, Delete None -- all fields visible
viewer Read only salary, ssn, homeAddress, personalEmail denied

Configuration

App configuration lives in Cargo.toml. The [package.metadata.app] table covers the schema, seed loaders, and static SPA; the [package.metadata.auth] table declares which auth methods are enabled along with JWT, OAuth provider, and role-mapping settings. There is no separate config.yaml or services.yaml.

[package]
name = "demo-authentication"
version = "1.0.0"
description = "Role-based access control with Basic, JWT, and OAuth login"

[package.metadata.app]
schemas = "schemas/auth.graphql"
dataLoader = "data/employees.json"
static = { path = "web", source = "source", spa = true, build = "npm install && npm run build" }

[package.metadata.app.authLoader]
roles = "data/roles.json"
users = "data/users.json"

[package.metadata.auth]
methods = ["basic", "jwt", "oauth"]

[package.metadata.auth.jwt]
secret = "${JWT_SECRET}"     # Set via environment variable
accessTtl = 900              # 15 minutes
refreshTtl = 604800          # 7 days

[package.metadata.auth.oauth.github]
clientId = "${GITHUB_CLIENT_ID}"
clientSecret = "${GITHUB_CLIENT_SECRET}"

[package.metadata.auth.oauth.google]
clientId = "${GOOGLE_CLIENT_ID}"
clientSecret = "${GOOGLE_CLIENT_SECRET}"

# Provider-based rules assign roles automatically on OAuth login.
[[package.metadata.auth.oauth.rules]]
strategy = "provider"
pattern  = "google"
role     = "admin"           # Google users -> admin role

[[package.metadata.auth.oauth.rules]]
strategy = "provider"
pattern  = "github"
role     = "viewer"          # GitHub users -> viewer role

Environment Variables

Variable Required Description
JWT_SECRET Yes Secret key for HS256 JWT signing
GITHUB_CLIENT_ID For OAuth GitHub OAuth app client ID
GITHUB_CLIENT_SECRET For OAuth GitHub OAuth app client secret
GOOGLE_CLIENT_ID For OAuth Google OAuth client ID
GOOGLE_CLIENT_SECRET For OAuth Google OAuth client secret

Basic auth and JWT work without any environment variables (JWT will use a default secret in development mode). OAuth requires valid provider credentials.

Seed Data

The demo ships with three data files that are loaded on startup:

File Purpose Records
data/employees.json Employee records for the RBAC demo 5
data/users.json Auth users (admin + user) 2
data/roles.json Auth roles (super_user + admin + viewer) 3

REST Endpoints

Employee (auto-generated from schema)

Endpoint Methods Description
/demo-authentication/Employee GET, POST List/create employees
/demo-authentication/Employee/{id} GET, PUT, DELETE Read/update/delete an employee
/demo-authentication/Employee?stream=sse GET Real-time SSE stream of changes

Auth (provided by yeti-auth extension)

Endpoint Method Description
/yeti-auth/login POST JWT login (username + password -> token pair)
/yeti-auth/login DELETE Logout (clear session cookie)
/yeti-auth/jwt_refresh POST Refresh an expired access token
/yeti-auth/oauth_login GET Initiate OAuth flow
/yeti-auth/oauth_callback GET OAuth provider redirect target
/yeti-auth/oauth_user GET Current OAuth session info
/yeti-auth/oauth_logout POST Clear OAuth session
/yeti-auth/oauth_providers GET List providers and role mappings
/yeti-auth/auth GET Auth status check

Project Structure

demo-authentication/
├── Cargo.toml               # App + auth configuration ([package.metadata.app|auth])
├── schemas/
│   └── auth.graphql         # Employee table with sensitive fields
├── data/
│   ├── employees.json       # 5 seed employee records
│   ├── users.json           # 2 seed users (admin, user)
│   └── roles.json           # 3 roles (super_user, admin, viewer)
├── source/                  # React/Vite frontend
│   ├── index.html
│   ├── package.json
│   ├── vite.config.ts
│   └── src/
│       ├── main.tsx                  # React entry point
│       ├── App.tsx                   # Thin shell with navigation
│       ├── api.ts                    # Fetch helpers
│       ├── types.ts                  # Shared TypeScript types
│       ├── utils.ts                  # JSON syntax highlighting
│       ├── components/
│       │   ├── LoginPage.tsx         # Demo's own auth method selector (Basic/JWT/OAuth)
│       │   └── Footer.tsx            # Shared UI primitives
│       ├── hooks/
│       │   └── useAuth.ts            # Auth state hook
│       ├── pages/
│       │   └── AuthPage.tsx          # Login + employee data panels
│       └── styles/
│           ├── _vars.css             # Per-app brand colors and shared tokens
│           ├── yeti.css              # Canonical Yeti stylesheet
│           └── index.css             # App-specific overrides
└── web/                     # Built output (generated by npm run build)

This demo intentionally ships its own components/LoginPage.tsx instead of the standard pages/Login.tsx template -- LoginPage is the subject of the demo, with first-class UI for switching between Basic, JWT, and each OAuth provider. Other yeti UI apps use the simpler shared pages/Login.tsx + useAuth template that wraps the SPA with three lines:

const auth = useAuth()
if (auth === null) return <Loading/>
if (!auth) return <Login/>
return <App/>

Otherwise the layout follows the standard yeti UI app structure: thin App.tsx, root utility modules (api.ts, types.ts, utils.ts), shared UI in components/, hooks in hooks/, pages in pages/, stylesheets in styles/ (yeti.css canonical, _vars.css per-app tokens, index.css overrides).


Comparison

demo-authentication Typical Auth Setup
Auth methods Basic + JWT + OAuth from [package.metadata.auth] Each method requires separate library/middleware
Field masking Declarative in role JSON Custom serialization logic per endpoint
Role assignment OAuth rules in Cargo.toml Manual user provisioning or custom mapping code
Seed data authLoader + dataLoader in config Migration scripts or manual setup
Password hashing Argon2id (OWASP params), automatic Choose library, configure parameters, test
Token refresh Built-in endpoint, automatic rotation Custom refresh logic and token storage
Frontend SPA with Vite, auto-built on deploy Separate build pipeline and deployment
Lines of auth code 0 (configuration only) Hundreds to thousands

Built with Yeti | The Performance Platform for Agent-Driven Development

About

OAuth, JWT, and Basic Auth with role-based access control. A Yeti demo.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages