Files
paskia/docs/Integration.md
T
LeoVasanko 68dccc1378 OAuth2 OpenID Connect provider support, API and DB refactoring (#3)
Allows Paskia to authenticate the user to a client site.
- User friendly client registration flow on the admin app
- Redirect-based authentication flow (per spec)
- Backchannel logout both ways to keep sessions synchronized
- Groups integrated with Paskia's permission system
- Adds email, preferred username and telephone fields on user profile
- All new user basic info layout to show the new information, better looks
- API and DB structures redesigned
- Various unrelated fixes to theming and layout
2026-02-18 02:40:27 +00:00

5.8 KiB

Integrating Paskia with your App

This guide covers frontend and backend integration with Paskia. For Caddy forward-auth setup, see Caddy configuration.

Frontend Integration

Using the paskia-js Module

The paskia JavaScript module provides utilities for API calls, session validation, and authentication overlays. Works with any framework or vanilla JS.

<script type="module">
  import { apiJson, apiFetch, SessionValidator } from 'https://cdn.jsdelivr.net/npm/paskia@latest/dist/paskia.js'
</script>

Or install to your project:

npm install paskia

API Fetch with Automatic Auth

Use apiJson or apiFetch for API calls. When a 401/403 response includes an auth URL, the authentication dialog appears automatically, then the request retries. The JSON variant is purely for convenience, doing JSON headers and conversions for you.

import { apiJson, apiFetch, AuthCancelledError } from 'paskia'

// JSON API call (sets Content-Type, parses response)
try {
  const data = await apiJson('/api/endpoint', { method: 'POST', body: { key: 'value' } })
} catch (e) {
  if (e instanceof AuthCancelledError) {
    // User cancelled auth dialog
  }
}

// Raw fetch with auth handling (returns Response object)
const response = await apiFetch('/api/endpoint')

For requests that shouldn't trigger auth dialogs, use standard fetch or our fetchJson.

Session Validation Polling

Keep sessions alive and detect when the user logs out or switches accounts:

import { SessionValidator } from 'paskia'

const validator = new SessionValidator(
  () => currentUser?.uuid,              // getter for current user ID
  (error) => handleSessionLost(error)   // callback when session is lost or user changes
)

validator.start()  // start polling (pauses on idle)
validator.stop()   // stop polling

The validator calls /auth/api/validate periodically to:

  • Renew the session cookie (24h lifetime)
  • Detect if the user logged out or switched accounts
  • Pause polling when the page is idle, allowing sessions to expire when not used

Manual Auth Flow

If you need custom control, handle 401/403 responses manually:

import { showAuthIframe, AuthCancelledError } from 'paskia'

const response = await fetch('/api/protected')
if (response.status === 401 || response.status === 403) {
  const data = await response.json()
  if (data.auth?.iframe) {
    try {
      await showAuthIframe(data.auth.iframe)
      // Retry the original request
    } catch (e) {
      if (e instanceof AuthCancelledError) {
        // User clicked Back
      }
    }
  }
}

User Info and Profile

Get current user details:

const user = await apiJson('/auth/api/user-info', { method: 'GET' })
// Returns: { uuid, display_name, credentials, sessions, permissions, ... }

Or link to the built-in profile page: /auth/

Backend Integration

Using Forward-Auth Headers

When using Caddy forward-auth, your backend receives Remote-* headers on authenticated requests. See Headers for the full list.

# Example: Python/FastAPI
@app.get("/api/data")
def get_data(request: Request):
    user_id = request.headers.get("Remote-User")
    org_id = request.headers.get("Remote-Org")
    permissions = request.headers.get("Remote-Groups", "").split(",")
    # ...

Direct Validation from Backend

Your backend can validate sessions directly by calling Paskia's validate endpoint:

import httpx

async def validate_session(request) -> dict:
    """Validate a session cookie and check permissions."""
    authcookie = request.get("__Host-paskia")
    response = await httpx.post(
        "http://localhost:4401/auth/api/validate?perm=myapp:login+myapp:api",
        headers={
            "Host": request.headers["host"]
            "X-Forwarded-For": request.client.host,
            "Cookie": f"__Host-paskia={}",
        },
    )
    if response.status_code != 200:
        return response.json()  # Return to client
    # User authenticated... We are good to go!
    ctx = response.json()  # User and session information

This is useful for:

  • WebSocket connections where headers aren't available after handshake
  • Background jobs that need to verify a stored session
  • APIs not behind forward-auth (auth/restrict)

Validate Endpoint Parameters

POST /auth/api/validate accepts query parameters:

Parameter Description
perm=scope:name Require this permission (repeatable)
max_age=5min Require recent passkey use

Returns 200 with user info on success, 401/403 on failure.

Proxying /auth/ to Paskia

Your app server needs to proxy /auth/ paths to Paskia. This can be done by your application but is much easier done by Caddy or Nginx.

Caddy

This handles both HTTP and WebSocket connections. Caddy's reverse_proxy handles HTTP and WebSocket transparently. This is essentially what our Caddy auth/setup snippet does: reverse_proxy :4401.

app.example.com {
    import auth/setup
    # ... your routes in handle blocks
}

Nginx

Certain headers need to be configured for correct host and WS support:

location /auth/ {
    proxy_pass http://localhost:4401;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
}

Node.js / Express

Using http-proxy-middleware:

import { createProxyMiddleware } from 'http-proxy-middleware'

app.use('/auth', createProxyMiddleware({ target: 'http://localhost:4401', ws: true, changeOrigin: false }))

Python / FastAPI

You will need to process and handle /auth/ for HTTP requests and /auth/ws/ for WebSockets manually, which is beyond the scope of this documentation.

We highly recommend Caddy instead as the simpler and more production-worthy solution that Just Works.