Gatekeeper

An independent authentication and authorization microservice implementing passkey-only authentication (WebAuthn/FIDO2) and fine-grained role-based access control with record-level permissions.

NOTE: There seems to be a mistake here. ABAC was not supposed to be part of this technology but was implemented anyway. We will need to remove this.

Overview

Gatekeeper provides:

  • Passwordless authentication - WebAuthn/FIDO2 passkeys only, no passwords
  • Role-based access control (RBAC) - Hierarchical roles with permission inheritance
  • Record-level permissions - Fine-grained access to specific resources
  • JWT sessions - Stateless session management with refresh tokens
  • Framework-agnostic - REST API for integration with any system

Projects

Project Description
Gatekeeper.Api REST API with WebAuthn and authorization endpoints
Gatekeeper.Migration Database schema using DataProvider migrations
Gatekeeper.Api.Tests Integration tests

Getting Started

Prerequisites

  • .NET 9.0 SDK
  • SQLite (default) or PostgreSQL

Run the API

cd Gatekeeper/Gatekeeper.Api
dotnet run

The API starts on http://localhost:5002.

Database Setup

The database is automatically created on first run. To reset:

rm gatekeeper.db
dotnet run

API Endpoints

Authentication

Endpoint Method Description
/auth/register/begin POST Start passkey registration
/auth/register/complete POST Complete passkey registration
/auth/login/begin POST Start passkey authentication
/auth/login/complete POST Complete authentication, returns JWT
/auth/logout POST Revoke current session
/auth/session GET Get current session info

Authorization

Endpoint Method Description
/authz/check GET Check if user has permission
/authz/permissions GET List user's effective permissions
/authz/evaluate POST Bulk permission check

Admin (requires admin role)

Endpoint Method Description
/admin/users GET/POST User management
/admin/roles GET/POST Role management
/admin/permissions GET/POST Permission management

Usage Examples

Register a Passkey

# 1. Begin registration
curl -X POST http://localhost:5002/auth/register/begin \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com", "displayName": "John Doe"}'

# Response contains WebAuthn options for the browser
# 2. Browser calls navigator.credentials.create() with options
# 3. Complete registration with authenticator response
curl -X POST http://localhost:5002/auth/register/complete \
  -H "Content-Type: application/json" \
  -d '{"challengeId": "...", "response": {...}}'

Authenticate

# 1. Begin login
curl -X POST http://localhost:5002/auth/login/begin \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com"}'

# 2. Browser calls navigator.credentials.get() with options
# 3. Complete login
curl -X POST http://localhost:5002/auth/login/complete \
  -H "Content-Type: application/json" \
  -d '{"challengeId": "...", "response": {...}}'

# Response: {"token": "eyJ...", "expiresAt": "..."}

Check Permission

curl "http://localhost:5002/authz/check?resource=patient&action=read&resourceId=123" \
  -H "Authorization: Bearer eyJ..."

# Response: {"allowed": true, "reason": "Role: physician"}

Database Schema

gk_user ──┬── gk_credential (passkeys)
          ├── gk_session (active sessions)
          ├── gk_user_role ── gk_role ── gk_role_permission
          ├── gk_user_permission (direct grants)
          └── gk_resource_grant (record-level access)
                                    │
                                    ▼
                              gk_permission

Key Tables

Table Purpose
gk_user User accounts (id, email, display_name)
gk_credential WebAuthn credentials (public_key, sign_count)
gk_session Active JWT sessions with revocation
gk_role Roles with optional parent hierarchy
gk_permission Permissions (resource_type + action)
gk_resource_grant Record-level permission grants

Permission Model

RBAC

admin (role)
  └── user:manage (permission)
  └── role:manage (permission)

physician (role)
  └── patient:read (permission)
  └── patient:write (permission)

Record-Level

User "dr.smith" has "patient:read" on Patient "patient-123"

Configuration

Environment variables:

Variable Default Description
JWT_SECRET (generated) Secret for JWT signing
JWT_ISSUER Gatekeeper JWT issuer claim
JWT_AUDIENCE GatekeeperClients JWT audience claim
JWT_EXPIRY_MINUTES 60 Token expiration
DATABASE_PATH gatekeeper.db SQLite database path
WEBAUTHN_RP_ID localhost WebAuthn Relying Party ID
WEBAUTHN_RP_NAME Gatekeeper WebAuthn Relying Party name
WEBAUTHN_ORIGIN http://localhost:5002 Expected origin

Testing

# Run all Gatekeeper tests
dotnet test --filter "FullyQualifiedName~Gatekeeper"

# Specific test class
dotnet test --filter "FullyQualifiedName~AuthorizationTests"

Design Principles

Following the repository's coding rules:

  • No exceptions - Returns Result<T, GatekeeperError> types
  • No classes - Uses records and static methods
  • No interfaces - Uses Func<T> for abstractions
  • Integration tests - Real database, no mocks
  • DataProvider - All SQL via generated extension methods

References

WebAuthn/FIDO2

Access Control

License

See repository root for license information.