Sign-In for Every Device
Let people use a password, passkey, or email code. You can also offer Google, Apple, GitHub, Microsoft, Discord, X, Meta, or an OpenID Connect provider.
Sign-In, Users, and Permissions API
Your customers can use passwords, passkeys, email codes, or a provider to sign in. AuthJet stores their records beside your app's data and enforces route permissions before your code runs. One sign-in covers every app you register on Substrio.
Route Gates
Decided by the platform before your code runs.
Sign-in providers built in
The Platform
You no longer have to keep a separate sign-in database in sync with your app. Your users are records in the database your app already uses.
Let people use a password, passkey, or email code. You can also offer Google, Apple, GitHub, Microsoft, Discord, X, Meta, or an OpenID Connect provider.
Require a second factor for every account member with one setting. Each member can use an authenticator app and gets ten recovery codes that work once.
Add the fields and indexes your app needs directly to its user records. You can add a customer before they sign in and use the same record after their first login.
Grant up to 64 named permissions for a team, store, or customer account. Declare the required permission next to the route. Revocation takes effect on the next request.
One Sign-In
Your store, admin dashboard, and partner portal can use the same sign-in through OpenID Connect. People sign in at your identity app once. Signing out there ends access across the connected apps.
Get Started
Configure your sign-in methods and declare which routes require a signed-in user or a named permission. Substrio enforces those rules before your handler runs.
Use the same Substrio account for every Jet. You can use AuthJet separately or with the other apps.
Configure your providers and roles in substrio.toml alongside the permission catalog. Apply the configuration with a deploy.
Add a gate word next to each route in routes.json. Requests without the required credentials or permissions are refused before your handler runs.
The API
A missing second factor returns a challenge. A denied permission returns 403 and names the required permission.
{
"success": true,
"user_id": 1,
"token": "p4sH7M...",
"keyring": "0xffffffffffffffff"
} {
"user_id": 1,
"email": "[email protected]",
"keyring": "0xffffffffffffffff",
"email_verified": false,
"mfa_enabled": false,
"locked": false,
"disabled": false,
"admin": false,
"superadmin": false,
"source": "native",
"created_at": 1791374435,
"last_login": 1791374435
} {
"error": "mfa_required",
"challenge": "substrio_mfa_...",
"expires_at": 1791374735,
"attempts_left": 5
} {
"error": "Permission denied",
"code": "PERMISSION_DENIED",
"status": 403,
"retryable": false,
"next_action": "request_permission",
"detail": "This route requires the permission \"ADMIN\""
} {
"names": [
{
"name": "READ",
"bit": 0,
"implies": []
},
{
"name": "WRITE",
"bit": 1,
"implies": ["READ"]
},
{
"name": "ADMIN",
"bit": 2,
"implies": ["READ", "WRITE"]
}
],
"all": "ADMIN"
} Security
Sessions are stored on the server and can be revoked immediately. New passwords are hashed with Argon2id, and authenticator secrets are encrypted with AES-256-GCM.
Sessions are stored on the server. When your user signs out, the session is refused on its next request in every app they signed in to.
New passwords are hashed with Argon2id. Imported bcrypt, PBKDF2, and Argon2id hashes continue to work.
Authenticator secrets are encrypted with AES-256-GCM. Recovery codes are stored only as SHA-256 digests.
A password reset returns the same response for an address with an account and an address without one.
After five wrong codes, a sign-in challenge cannot be used again. Five consecutive failures block recovery and MFA changes for 15 minutes.
Changes to keys, settings, and rules are recorded in a chain of digests. You can verify the chain end to end.