Porta v1.11.0
Skip to content

Authentication Modes ​

Porta supports multiple authentication methods that can be configured per organization and per client. This page covers all login methods, password-login two-factor authentication options, and how they are configured and enforced.

Overview ​

Porta provides a layered authentication system:

LayerMethodsDescription
Primary authenticationPassword, Magic LinkHow users prove their identity
Password-login 2FAEmail OTP, TOTP, Recovery CodesAdditional verification after a password

The 2FA policy applies only when a user signs in with a password. A validated magic link is already a complete passwordless proof, so Porta does not add an email OTP or TOTP prompt after it.

Login Method Matrix ​

ConfigurationLogin Page ShowsUse Case
[password, magic_link]Password form + magic link button + forgot passwordDefault — maximum flexibility
[password]Password form + forgot password onlyTraditional enterprise apps
[magic_link]Email input + magic link button onlyPasswordless-first experience

Password Authentication ​

Traditional email + password authentication with enterprise-grade security.

How It Works ​

Password Security ​

FeatureImplementation
Hashing algorithmArgon2id (winner of the Password Hashing Competition)
Password validationNIST SP 800-63B compliant
Minimum length8 characters (configurable)
Breach detectionChecks against known breached passwords
Fixed verification workOne account or dummy Argon2id verification per valid-shaped attempt
Rate limitingRedis-backed per-email rate limiting on login attempts

Password Reset Flow ​

When password login is enabled, users can reset forgotten passwords:

Password-reset requests do not perform account lookup, token creation, or SMTP delivery in the public request. A bounded worker performs that work after the generic response has been returned. Retries reuse the same job-owned artifact; an ambiguous SMTP outcome can deliver the same link again, but cannot create a second active reset token for that job.


Passwordless authentication via secure one-time email links. Users click a link in their email to log in — no password needed.

How It Works ​

FeatureImplementation
Token generationCryptographically secure random tokens
Token expiryConfigurable (default: 15 minutes)
Single useTokens are invalidated after first use
Rate limitingPrevents email flooding attacks
User enumeration protectionSame response regardless of whether email exists
Tenant and session bindingLink authority is fixed to one tenant and optional OIDC interaction

The callback route treats its organization and interaction values as untrusted transport input. They must match the authority stored when the link was issued. A mismatch returns the same generic failure as an invalid or expired link and does not consume the artifact. Once the database transaction succeeds, the link stays consumed even if the short-lived Redis continuation cannot be created; the user must request a new link rather than replaying the committed one.

Magic link is ideal for:

  • Consumer applications where users dislike remembering passwords
  • Internal tools where email access implies authorization
  • Mobile-first experiences where typing passwords is cumbersome
  • Low-friction onboarding where you want to minimize signup steps

Password-Login Two-Factor Authentication (2FA) ​

After successful password authentication, Porta can require a second factor. This policy does not run after magic-link authentication.

Email OTP ​

A 6-digit one-time password sent to the user's email.

FeatureDetail
Code format6 numeric digits
DeliveryEmail via configured SMTP
ExpiryConfigurable (default: 10 minutes)
Max active codes3 per user (prevents flooding)
TemplateCustomizable via emails/otp-code.hbs

User flow:

  1. User completes password login
  2. Porta generates a 6-digit code and stores it in the database
  3. Code is emailed to the user
  4. User enters the code on the verification page
  5. Porta validates the code (not expired, not used)
  6. Authentication is complete

TOTP (Authenticator Apps) ​

Time-based One-Time Password using authenticator apps like Google Authenticator, Authy, Microsoft Authenticator, or any TOTP-compatible app.

FeatureDetail
AlgorithmTOTP (RFC 6238)
Code format6 numeric digits, 30-second window
SetupQR code scanning or manual secret entry
Secret storageAES-256-GCM encrypted in PostgreSQL
VerificationTime-window tolerance (±1 step)

Setup flow:

  1. User navigates to 2FA setup
  2. Porta generates a TOTP secret and encrypts it with AES-256-GCM
  3. QR code is displayed for scanning with an authenticator app
  4. User enters a verification code from their app to confirm setup
  5. Recovery codes are generated and displayed (one-time view)

Login flow:

  1. User completes password login
  2. Porta detects TOTP is configured for this user
  3. User enters the current 6-digit code from their authenticator app
  4. Porta verifies the code against the encrypted secret
  5. Authentication is complete

Recovery Codes ​

One-time backup codes for account recovery when the primary 2FA method is unavailable (lost phone, no email access).

FeatureDetail
Format8-character alphanumeric codes with dash (e.g., A1B2-C3D4)
Count10 codes generated per setup
StorageArgon2id hashed (not stored in plain text)
UsageEach code can be used exactly once
Case-insensitivea1b2-c3d4 matches A1B2-C3D4
Dash-insensitiveA1B2C3D4 matches A1B2-C3D4

Important

Recovery codes are shown only once during 2FA setup. Users should save them in a secure location. If all recovery codes are used and the primary 2FA method is lost, an admin must manually disable 2FA for the user.


Per-Organization Configuration ​

Each organization has a defaultLoginMethods setting. Clients configured to inherit login methods use this non-empty selection.

Setting Organization Login Methods ​

Via CLI:

bash
# Enable both password and magic link (default)
porta org update <id-or-slug> --login-methods password,magic_link

# Password only
porta org update <id-or-slug> --login-methods password

# Magic link only
porta org update <id-or-slug> --login-methods magic_link

Via Admin API:

bash
curl -X PUT https://porta.local:3443/api/admin/organizations/<org-id> \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "defaultLoginMethods": ["password", "magic_link"] }'

2FA Organization Policy ​

Password-login 2FA enforcement is configured per organization:

PolicyPassword-login behavior
optionalUse a user's enrolled method when enabled; otherwise continue
required_emailRequire an email one-time password
required_totpRequire authenticator/TOTP enrollment and verification
required_anyRequire an available email OTP or TOTP method

The embedded Admin UI edits this policy on the selected organization's Authentication tab. The SDK exposes the same resource through porta.twoFactor.getPolicy(organizationId) and porta.twoFactor.setPolicy(organizationId, policy). The policy endpoint uses admin:org:update; the Admin UI reloads the displayed policy after a failed partial save and does not retry automatically.


Per-Client Overrides ​

Individual clients can override the organization's default login methods. This allows different applications within the same organization to offer different login experiences.

Setting Client Login Methods ​

Via CLI:

bash
# Override to password only for this client
porta client update <client-id> --login-methods password

# Override to magic link only
porta client update <client-id> --login-methods magic_link

# Clear override (inherit from organization)
porta client update <client-id> --clear-login-methods

Via Admin API:

bash
# Set override
curl -X PUT https://porta.local:3443/api/admin/clients/<client-id>/login-methods \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "loginMethods": ["password"] }'

# Clear override (inherit from org)
curl -X DELETE https://porta.local:3443/api/admin/clients/<client-id>/login-methods \
  -H "Authorization: Bearer $TOKEN"

Resolution Logic ​

When a user initiates a login, Porta determines the available login methods using this resolution logic:

Enforcement Points ​

Login methods are enforced at five endpoints, before any user lookup or CSRF validation:

  1. GET /interaction/:uid — Login page rendering
  2. POST /interaction/:uid/magic-link — Magic link request
  3. GET /interaction/:uid/forgot-password — Password reset form
  4. POST /interaction/:uid/forgot-password — Password reset submission
  5. POST /interaction/:uid/reset-password — New password submission

If a user attempts to use a disabled login method, Porta responds with:

  • HTTP 403 status code
  • Audit event: security.login_method_disabled
  • Error message explaining the method is not available

Security Considerations ​

Rate Limiting ​

All authentication endpoints are rate-limited to prevent brute-force attacks:

EndpointLimitWindow
Password loginConfigurablePer email + org
Magic link requestConfigurablePer email + org
2FA code verificationConfigurablePer user
Password reset requestConfigurablePer email

Audit Logging ​

Every authentication event is logged for security monitoring:

EventLogged Data
auth.login.successUser ID, method, client, org, IP
auth.login.failureEmail attempted, failure reason, org, IP
auth.magic_link.sentEmail, org, IP
auth.magic_link.verifiedUser ID, org
auth.2fa.verifiedUser ID, method (otp/totp/recovery)
auth.2fa.failedUser ID, method, failure reason
auth.password_reset.requestedEmail, org
auth.password_reset.completedUser ID, org
security.login_method_disabledAttempted method, client, org

Enumeration-Resistant Operations ​

Porta does not use wall-clock timing thresholds as a security guarantee. Instead, every admitted password attempt performs one Argon2id verification: an eligible account hash is used when one exists, otherwise Porta verifies a process-cached Argon2id dummy hash. Failed attempts also use the same persistence-operation shape and return the same generic public response.

Magic-link and password-reset requests enqueue tenant-bound recovery work before returning the generic response. Account lookup, token creation, and email delivery happen later in a bounded worker. Requests for absent or ineligible accounts complete as private no-ops, so the public request path does not reveal account state through account-dependent token or SMTP work.


Next Steps ​

Released under the MIT License.