- Serve multiple domains (RP IDs) from one instance: host-based dispatch, per-domain credentials and sessions, domains managed at runtime in the admin UI — previously one RP per instance - Cross-domain sign-in via Related Origin Requests: per-domain related-origins list with a served .well-known/webauthn document - Explicit per-domain origin lists with shell-glob wildcards (**. for apex + any subdomain depth, *. for one level), editable in the admin UI with validation and self-lockout guards - Per-domain auth hosts: the account/admin UI can live on a different host per domain, no longer confined to subdomains of a single RP - CLI: 'paskia init <rp-id [rp-name]' initializes or adds a domain to an existing database; 'paskia migrate' converts legacy databases BREAKING CHANGES (v2.0): - Database schema: config is now per-domain and credentials/sessions carry an rp_id — existing databases must be converted with 'paskia migrate' - Origins are now explicit: main implicitly allowed every subdomain of the RP; configure '**.' origins to reproduce that behavior - CLI: the flat '--rp-id/--rp-name/--origin/--auth/--save' flags are replaced by the 'init' and 'migrate' subcommandsReviewed-on: #4
238 lines
9.7 KiB
Markdown
238 lines
9.7 KiB
Markdown
# Paskia
|
|
|
|

|
|
|
|
An easy to install passkey-based authentication service that protects any web application with strong passwordless login.
|
|
|
|
## What is Paskia?
|
|
|
|
- Easy to use fully featured auth&auth system (login and permissions)
|
|
- Organization and role-based access control
|
|
* Org admins control their users and roles
|
|
* Multiple independent orgs
|
|
* Master admin can do everything or delegate to org admins
|
|
- User Profile and Admin by API and web interface
|
|
- Implements login/reauth/forbidden flows for you
|
|
- Single Sign-On (SSO): Users register once and authenticate across your services
|
|
- Remote autentication by entering random keywords from another device (like 2fa)
|
|
- No CORS, NodeJS or anything extra needed.
|
|
|
|
## Authenticate to get to your app, or in your app
|
|
|
|
- API fetch: auth checks and login without leaving your app
|
|
- Forward-auth proxy: protect any unprotected site or service ([Caddy](docs/proxy/caddy.md), [Nginx](docs/proxy/nginx.md), and [others](docs/proxy/index.md))
|
|
|
|
The API mode is useful for applications that can be customized to run with Paskia. Forward auth can also protect your javascript and other assets. Each provides fine-grained permission control and reauthentication requests where needed, and both can be mixed where needed.
|
|
|
|
## Authentication flows already done
|
|
|
|

|
|
**Automatic light/dark mode switching with overrides by user profile and protected app's theme.**
|
|
|
|
Paskia includes set of login, reauthentication and forbidden dialogs that it can use to perform the needed flows. We never leave the URL, no redirections, and if you make use of API mode, we won't even interrupt whatever your app was doing but retry the blocked API fetch after login like nothing happened.
|
|
|
|
## Quick Start
|
|
|
|
Install [UV](https://docs.astral.sh/uv/getting-started/installation/) and run:
|
|
|
|
```sh
|
|
uvx paskia init example.com
|
|
uvx paskia
|
|
```
|
|
|
|
The first command bootstraps the database and prints a registration link for the Admin. The second starts the server on [localhost:4401](http://localhost:4401), serving authentication for `*.example.com`. For local testing, leave out the rp-id (defaults to `localhost`).
|
|
|
|
For production you need a web server such as [Caddy](https://caddyserver.com/) to serve HTTPS on your actual domain names and proxy requests to Paskia and your backend apps (see documentation below).
|
|
|
|
For a permanent install of `paskia` CLI command, not needing `uvx`:
|
|
|
|
```sh
|
|
uv tool install paskia
|
|
```
|
|
|
|
## Configuration
|
|
|
|
Bootstrapping is done once with `paskia init`; after that, `paskia` serves all configured domains from the database `paskia.kantadb` in the current directory. Domain configuration (rp-name, auth host, origins) is managed via the admin web interface, including adding further domains (rp-ids).
|
|
|
|
```text
|
|
paskia init [rp-id] [rp-name] [options] # one-time bootstrap; with an existing
|
|
# database, adds the rp-id (or renames it)
|
|
paskia migrate [rp-id] # convert a legacy {rp-id}.paskiadb database
|
|
paskia [-l endpoint] # serve
|
|
```
|
|
|
|
| init option | Description | Default |
|
|
|--------|-------------|---------|
|
|
| -l, --listen *endpoint* | Listen address: *host*:*port*, :*port* (all interfaces), or */path.sock* (stored in the database) | **localhost:4401** |
|
|
| *rp-id* (positional) | Main/top domain for passkeys | **localhost** |
|
|
| *rp-name* (positional) | Branding name of the domain (passkey auth, login dialog) | Same as rp-id |
|
|
|
|
Origins, auth hosts and related domains are configured afterwards in the admin panel's Domains section.
|
|
|
|
The `paskia` serve command accepts only `--listen` (overriding the stored value) and never converts databases: with no `paskia.kantadb` it tells you to run `paskia init`, or `paskia migrate` when a legacy `{rp-id}.paskiadb` database is present. `paskia migrate` converts the legacy database; with several candidates, the positional rp-id selects one by name and the rest are left in place.
|
|
|
|
## Tutorial: From Local Testing to Production
|
|
|
|
This section walks you through a complete example, from running Paskia locally to protecting a real site in production.
|
|
|
|
### Step 1: Production Configuration
|
|
|
|
For a real deployment, bootstrap Paskia with your domain name (rp-id). This enables SSO setup for that domain and any subdomains.
|
|
|
|
```sh
|
|
uvx paskia init example.com "Example Corp"
|
|
uvx paskia
|
|
```
|
|
|
|
This binds passkeys to the rp-id, allowing them to be used there or on any subdomain of it. The rp-name is the branding shown in UI and registered with passkeys for everything on your domain (rp id). Init prints a registration link—use it to create your Admin account. You may enter your real name here for a more suitable account name.
|
|
|
|
### Step 2: Set Up Caddy
|
|
|
|
Install [Caddy](https://caddyserver.com/) and copy the [auth folder](caddy/auth) to `/etc/caddy/auth`. Say your current unprotected Caddyfile looks like this:
|
|
|
|
```caddyfile
|
|
app.example.com {
|
|
reverse_proxy :3000
|
|
}
|
|
```
|
|
|
|
Add Paskia full site protection:
|
|
|
|
```caddyfile
|
|
app.example.com {
|
|
import auth/setup
|
|
handle {
|
|
import auth/require perm=myapp:login
|
|
reverse_proxy :3000
|
|
}
|
|
}
|
|
```
|
|
|
|
Run `systemctl reload caddy`. Now `app.example.com` requires the `myapp:login` permission. Try accessing it and you'll land on a login dialog.
|
|
|
|
### Step 3: Assign Permissions via Admin Panel
|
|
|
|

|
|
|
|
1. Go to `app.example.com/auth/admin/`
|
|
2. Create a permission, give it a name and scope `myapp:login`
|
|
3. Assign it to Organization
|
|
4. In that organization, assign it to the Administration role
|
|
|
|
Now you have granted yourself the new permission.
|
|
|
|
Permission scopes are text identifiers with colons as separators that we can use for permission checks. The `myapp:` prefix is a convention to namespace permissions per application—you but you can use other forms as you see fit (urlsafe characters, no spaces allowed).
|
|
|
|
### Step 4: Add API Authentication to Your App
|
|
|
|
Your backend already receives `Remote-*` headers from Caddy's forward-auth. For frontend API calls, we provide a [JS paskia module](https://www.npmjs.com/package/paskia):
|
|
|
|
```js
|
|
import { apiJson } from 'https://cdn.jsdelivr.net/npm/paskia@latest/dist/paskia.js'
|
|
|
|
const data = await apiJson('/api/sensitive', { method: 'POST' })
|
|
```
|
|
|
|
When a 401/403 occurs, the auth dialog appears automatically, and the request retries after authentication.
|
|
|
|
To protect the API path with a different permission, update your Caddyfile:
|
|
|
|
```caddyfile
|
|
app.example.com {
|
|
import auth/setup
|
|
|
|
@api path /api/*
|
|
handle @api {
|
|
import auth/require perm=myapp:api
|
|
reverse_proxy :3000
|
|
}
|
|
|
|
handle {
|
|
import auth/require perm=myapp:login
|
|
reverse_proxy :3000
|
|
}
|
|
}
|
|
```
|
|
|
|
Create the `myapp:api` permission in the admin panel, that will be required for all API access. Link to `/auth/` for the built-in profile page.
|
|
|
|
You may also remove the `myapp:login` protection from the rest of your site paths, unless you wish to keep all your assets behind a login page. Having this as the last entry in your config allows free access to everything not matched by other sections.
|
|
|
|
```Caddyfile
|
|
handle {
|
|
reverse_proxy :3000
|
|
}
|
|
```
|
|
|
|
### Step 5: Run Paskia as a Service
|
|
|
|
Create a system user paskia, install UV on the system, and create a systemd unit:
|
|
|
|
```sh
|
|
sudo useradd --system --home-dir /srv/paskia --create-home paskia
|
|
```
|
|
|
|
Install UV on the system (or arch btw `pacman -S uv`):
|
|
|
|
```sh
|
|
curl -LsSf https://astral.sh/uv/install.sh | sudo env UV_INSTALL_DIR=/usr/local/bin sh
|
|
```
|
|
|
|
Create a systemd unit:
|
|
|
|
```sh
|
|
sudo systemctl edit --force --full paskia.service
|
|
```
|
|
|
|
Paste the following and save:
|
|
|
|
```ini
|
|
[Unit]
|
|
Description=Paskia
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=paskia
|
|
WorkingDirectory=/srv/paskia
|
|
ExecStart=uvx paskia@latest
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
Run the service and view log:
|
|
|
|
```sh
|
|
sudo systemctl enable --now paskia && sudo journalctl -n30 -ocat -fu paskia
|
|
```
|
|
|
|
### Optional: Dedicated Authentication Site
|
|
|
|
Add a Caddy configuration for the authentication domain:
|
|
|
|
```caddyfile
|
|
auth.example.com {
|
|
reverse_proxy :4401
|
|
}
|
|
```
|
|
|
|
Now all authentication happens at `auth.example.com` instead of `/auth/` paths on your apps. Your existing protected sites continue to work as before but they just forward to the dedicated site for user profile and other such functionality.
|
|
|
|
Set the auth host in the admin panel's Domains section.
|
|
|
|
## Multiple Domains and Related Origins
|
|
|
|
One Paskia instance can serve several domains (rp-ids) from the same database: users, orgs and permissions are shared, while passkeys are registered per domain. The master admin adds domains in the admin panel's Domains section; no restart is needed.
|
|
|
|
A domain can also let *other* domain names use its passkeys via WebAuthn [Related Origin Requests](https://passkeys.dev/docs/advanced/related-origins/) — list them in the domain's allowed origins (they show as related domains), and paskia serves the required `/.well-known/webauthn` declaration on the domain's main site. In-domain entries of the same list restrict which sites of the domain's own name may authenticate (a new domain defaults to `**.{domain}` — the apex and all subdomains over https).
|
|
|
|
See [Multi-Site documentation](docs/MultiSite.md) for details.
|
|
|
|
|
|
## Further Documentation
|
|
|
|
- [Forward-Auth Guides](https://git.zi.fi/LeoVasanko/paskia/src/branch/main/docs/proxy/index.md) (Caddy, Nginx, ...)
|
|
- [Trusted Headers for Backend Apps](https://git.zi.fi/LeoVasanko/paskia/src/branch/main/docs/Headers.md)
|
|
- [Frontend integration](https://git.zi.fi/LeoVasanko/paskia/src/branch/main/docs/Integration.md)
|
|
- [Paskia API](https://git.zi.fi/LeoVasanko/paskia/src/branch/main/docs/API.md)
|