Authentication and Access Control

Password rules, email verification, social sign-in, TOTP two-factor, sessions, API keys, and the organization role model.


How developers sign in to DeployYourApp, how CI authenticates, and how permissions are decided. Authentication is built on Better Auth with a PostgreSQL adapter.

Email and password

The default method. You register with an email address and a password.

Property Value
Hash function scrypt
Parameters N=16384, r=16, p=1, dkLen=64
Salt 16 random bytes per password
Stored form hex(salt):hex(derivedKey)
Comparison Constant-time
Minimum length 8 characters

Plaintext passwords are never stored and never logged.

Email verification is required. You cannot sign in until you follow the link in the verification email.

Sign in at /auth/login in the dashboard.

Signing in from the CLI

dya login

The CLI prints an authorization URL, waits for you to press ENTER before opening a browser, and then polls until you approve. Nothing listens on your machine, so this also works over SSH, inside containers, and when the browser runs on a different host.

Four things constrain that flow:

  • The approval page shows which client is asking, the IP the request came from, and when — so a request you did not start is recognisable before you approve it.
  • The poll token is a separate secret from the session id. The id appears in the URL you might paste into chat or screen-share; the token travels only in the X-Poll-Token header, and is stored server-side as a SHA-256 hash.
  • Approval is single-use. Claiming is a conditional update, so two racing polls cannot both mint a token — the loser gets 410.
  • A pending request expires after 10 minutes, and denying in the browser fails the waiting terminal immediately.

If your account has 2FA enabled, the browser enforces it before the request can be approved. The CLI never handles your password or authenticator secret — only the session token.

CLI 0.1.x used a localhost callback on ports 98769895, constrained to a loopback URL with a one-time state nonce. The server still accepts that flow so installed copies keep working.

CLI session tokens are 32 random bytes, hex-encoded, and expire after 30 days. They are recorded with the user agent DeployYourApp CLI and appear in your active sessions list.

For CI, use an API key instead:

dya login --api-key dya_...

Social sign-in

Three providers are supported, each enabled only when the platform operator has configured both its client ID and client secret:

  • Google
  • GitHub
  • Microsoft (multi-tenant, tenantId: common)

Social sign-in is available on every plan.

Two-factor authentication

Two-factor uses time-based one-time passwords.

Property Value
Algorithm TOTP
Digits 6
Period 30 seconds
Backup codes Issued at enrollment

Enabling it

  1. Go to Settings > Security in the dashboard.
  2. Click Enable 2FA and confirm with your password.
  3. Scan the QR code with an authenticator app.
  4. Save the backup codes shown on screen — they are displayed once.
  5. Enter the 6-digit code to confirm enrollment.

At sign-in you are prompted for a code after your password. You can answer with a backup code instead if you have lost your authenticator.

Organization two-factor policy

Owners and admins can enforce two-factor for the whole organization under Settings > Organization > Security Policy.

Setting Effect
Require two-factor authentication Members without 2FA on their account are blocked from every organization resource with TWO_FACTOR_REQUIRED. API keys inherit the 2FA state of the user who owns them. You must have 2FA enabled yourself before you can turn this on.
Require a 2FA code for bundle uploads Every bundle upload must carry a fresh authenticator code in the X-2FA-Code header. dya deploy prompts for it when the server answers TWO_FACTOR_CODE_REQUIRED.

Upload codes are single-use. The server records the consumed time step in Redis for two periods, so a code captured from a CI log cannot be replayed to push a second bundle. A replayed code is rejected with TWO_FACTOR_CODE_REPLAYED.

Sessions

Dashboard and CLI sessions are database-backed cookie sessions, not JWTs. The cookie prefix is dya.

Property Value
Dashboard session lifetime 7 days
CLI session lifetime 30 days
Refresh interval 24 hours

The API also accepts Authorization: Bearer <session token>, which is how the single-page dashboard and the CLI authenticate.

Cross-origin requests are restricted to an explicit origin allow-list. There is no subdomain wildcard.

View and revoke sessions under Settings > Security > Active Sessions. Each entry shows the device or browser, IP address, and last-active time. Use Revoke for one session, or revoke all others to sign out every other device.

Suspending an account invalidates access immediately on both the session and API key paths — it does not wait for the session to expire.

API keys

API keys give programmatic access without an interactive login.

Property Value
Format dya_ followed by 32 random bytes, hex-encoded
Storage SHA-256 hex hash. The raw key is never stored
Shown in full Once, at creation
Scope binding One organization, fixed at creation
Expiry Optional, set in days at creation

A key is rejected if it is revoked, expired, or owned by a suspended user. Every accepted request updates the key's last-used timestamp.

A key created in one organization cannot be used against another, even one its creator belongs to.

Creating a key

  1. Go to Settings > API Keys.
  2. Click Create API Key.
  3. Name it, select scopes, and optionally set an expiry.
  4. Copy the key immediately.

Or via the API:

curl -X POST https://api.deployyour.app/api/orgs/$ORG_ID/api-keys \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $ADMIN_KEY" \
  -d '{"name":"ci-deploy","scopes":["app:read","channel:read","bundle:upload","deployment:create"],"expiresInDays":90}'

scopes defaults to ["*"] if omitted. Unknown scope strings are rejected with a validation error.

Using a key

dya login --api-key dya_...
dya deploy

Or as a header on any API request:

X-API-Key: dya_...

Scopes

Scopes are permission strings — the same vocabulary the role model uses. A key is limited to the scopes assigned to it, and is additionally limited by the role of the user who created it.

Scope Grants
* Every permission
app:read app:create app:update app:delete App management
channel:read channel:create channel:update channel:delete Channel management
bundle:read bundle:upload bundle:delete Bundle management
deployment:read deployment:create deployment:rollback Deployment control
device:read View devices
member:invite member:remove member:update-role Membership management
settings:read settings:write Organization settings
apikey:create apikey:revoke API key management
billing:read billing:manage Billing
audit:read Audit log
build:read build:trigger build:cancel Cloud builds

A typical CI deploy key needs only:

["app:read", "channel:read", "bundle:upload", "deployment:create"]

Handling keys well

  • One key per pipeline or integration, so a single revocation is surgical.
  • Assign the narrowest scope set that works.
  • Set an expiry on anything temporary.
  • Revoke immediately when a key is no longer needed.
  • Never commit a key to version control.

Roles and permissions

An organization has four roles. The creator of an organization becomes its owner. An organization holds up to 100 members, and a user can create up to 5 organizations.

Permission Owner Admin Developer Viewer
app:read yes yes yes yes
app:create app:update app:delete yes yes
channel:read yes yes yes yes
channel:create channel:update channel:delete yes yes
bundle:read yes yes yes yes
bundle:upload yes yes yes
bundle:delete yes yes
deployment:read yes yes yes yes
deployment:create yes yes yes
deployment:rollback yes yes
device:read yes yes yes yes
member:invite member:remove member:update-role yes yes
settings:read yes yes yes yes
settings:write yes yes
apikey:create apikey:revoke yes yes
billing:read yes yes
billing:manage yes
audit:read yes yes
build:read yes yes yes yes
build:trigger yes yes yes
build:cancel yes yes

Owners hold the wildcard permission *. Cloud builds are currently disabled by the CLOUD_BUILDS_ENABLED feature flag, so the build:* permissions have no effect yet.

Every organization-scoped request re-checks membership against the database. A stale role carried on the request object is never trusted.

Security notification emails

You receive an automated email whenever a sensitive change is made to your account.

Event Sent when
Password changed Your password is changed from Settings > Profile
Password reset A password-reset link is used to set a new password
2FA enabled Two-factor enrollment completes
2FA disabled Two-factor is turned off

Each email includes the time, IP address, and device that made the change. These cannot be disabled. If one arrives that you do not recognise, reset your password and review your active sessions.

Account deletion

Deleting your account is self-serve, and confirmation is by email rather than by session alone. Requesting deletion sends a link that expires in 24 hours; clicking it performs the deletion.

The request is refused up front if your account is the sole owner of a live organization. Transfer ownership or delete the organization first.

Rate limits

Limits are the same on every plan and are keyed by IP address, authenticated or not. Rate limiting runs before authentication, so the identity behind a request is not yet known when the counter is chosen — and keying on an unverified credential from the request would let anyone mint a fresh bucket per attempt. One consequence is worth planning for: clients behind shared NAT, a corporate egress IP, or a CI runner share a bucket.

Redis backs the counters. The /health and /ready endpoints are exempt.

Endpoint Limit Window
Global default, all API requests 100 1 minute
Sensitive auth paths (sign-in, sign-up, password reset, verification email, 2FA verify/enable/disable), per IP 20 15 minutes
Bundle upload 30 1 hour
Device update check POST /api/update 60 1 minute
Analytics ingestion POST /api/stats 100 1 minute
Organization invitations 20 1 hour
Personal data export GET /api/me/export 5 1 hour
Corporate shell access-code lookup 10 1 hour
Notification unsubscribe 30 1 hour

Exceeding a limit returns 429:

{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests. Please try again later."
  }
}

Recommendations

  1. Enable 2FA on every account, and turn on the organization-wide requirement once your team has.
  2. Turn on the 2FA-for-uploads policy if a compromised CI key would be unacceptable.
  3. Give CI the narrowest API key scopes that work, and set an expiry.
  4. Review active sessions periodically and revoke what you do not recognise.
  5. Watch the audit log for unexpected activity. It is available on the Scale plan and above.

Previous
Encryption
Next
SDK Data Collection