Add Identity, Secrets, and Integrations
Connect real users and external services while keeping credentials out of code and authorization on the server.
By the end
An authenticated, least-privilege app with per-user data rules, protected secrets, integration receipts, and tested denial paths.
Design user-data scope
Decide whether a record belongs to one user, a team, an organization, or the entire application. Store the relevant ownership relationship and filter every query by the authenticated scope. Test horizontal access: one ordinary user must not retrieve or mutate another user's record by changing a URL or request identifier.
Administrative capability needs its own boundary and audit history. Avoid a single hidden boolean that unlocks everything without explanation. Name administrative actions, require appropriate roles, and record who performed them. Consider a separate admin view when ordinary and privileged workflows differ substantially.
Secrets are runtime configuration
API keys, tokens, signing secrets, and connection strings do not belong in source code, prompts, screenshots, or Git history. Store them in Replit's Secrets tool and read them as environment variables on the server. The browser should receive only values explicitly designed to be public.
A secret has a lifecycle: creation, least-privilege scope, named owner, rotation, revocation, and incident response. Keep development and production values separate. If a secret is exposed, remove it from visible history where practical, revoke it at the provider, create a replacement, update the intended environment, and verify the old value no longer works.
Secrets are encrypted storage, not isolation from every collaborator who can run the app. A Multiplayer collaborator can view App Secret values. In an organization, a non-owner may not see a value in the pane but can still access it through the running environment. Treat project edit or runtime access as potential secret access, invite only trusted collaborators, prefer narrowly scoped development credentials, and revoke access when collaboration ends.
Make dependency failure a product state
External services fail. Design pending, succeeded, failed, and retryable states. Store a safe provider reference and last attempt time. Tell the user whether their request was accepted locally, whether the external action completed, and what will happen next. Never show success before the authoritative system confirms it.
Set timeouts and bounded retry policies. Retry automatically only when the operation is idempotent or protected by a stable idempotency key. For ambiguous outcomes, reconcile by querying the provider with the local operation identifier rather than sending the action again blindly.
Working reference
Commands and patterns
Verify configuration without disclosure
node - <<'NODE' const required = ['DATABASE_URL', 'INTEGRATION_API_KEY']; console.log(Object.fromEntries(required.map((key) => [key, Boolean(process.env[key])]))); NODE
Report only presence. Never echo, log, or paste the values of credentials into Agent chat or public evidence.
Secret-pattern filename check
git grep -Il -E '(API[_-]?KEY|SECRET|TOKEN|PASSWORD)[[:space:]]*[:=]' -- ':!*.lock' || true
The -l form prints only matching filenames, not the lines that may contain credentials. Treat every match as sensitive, inspect it locally without pasting values into Agent chat, and use Replit Security Center or a dedicated secrets scanner for the real review.
When the happy path breaks
Failure modes
Symptom
A signed-in user can open another user's record by changing an ID in the URL.
Likely cause
The app authenticates users but does not scope server-side reads and writes by ownership or organization.
Recovery
Apply server authorization to every operation, query within the actor's scope, and add cross-user denial tests.
Symptom
A key appears in source history, logs, browser code, or an Agent transcript.
Likely cause
The credential was treated as ordinary configuration or debug output.
Recovery
Revoke and rotate it immediately, move the replacement to Secrets, limit scope, and add source and client-bundle checks.
Symptom
A retry sends the same external notification or creates the same record twice.
Likely cause
The app lacks an idempotency key, durable attempt state, or provider reconciliation.
Recovery
Assign a stable operation ID, persist each attempt and provider ID, and reconcile ambiguous results before retrying.
Symptom
Preview works, but the published app cannot authenticate or call the integration.
Likely cause
Development and published environments have different domains, callback configuration, secrets, or resource scope.
Recovery
Maintain an environment configuration matrix and verify callback URLs, domains, and secret presence separately before republishing.
Hands-on lab
Add an authenticated approval with a safe integration
Require sign-in for the approval workflow, restrict records by scope, and send one synthetic external action with durable evidence.
- 01Choose Replit Auth or Clerk Auth using the account and branding tradeoff above. Use Agent to provision it and connect the authenticated identity to database users; record that development and production identities are separate where applicable.
- 02Define requester, reviewer, and administrator capabilities and enforce each protected operation on the server.
- 03Create two synthetic development users. Use separate browser profiles or private sessions to prove each is denied access to the other's private records; never reuse a production identity for this test.
- 04Add a development-only FAKE_PROVIDER_TOKEN through Secrets. Record its name, owner, and scope but never its value. Confirm every collaborator with project runtime access is trusted to access it.
- 05Ask Agent to create a development-only POST /api/fake-provider/notifications endpoint. It must return 404 when REPLIT_DEPLOYMENT equals 1, require the server-supplied fake token, and accept { operationId, requestId, event, scenario }. Store fake provider operations under a unique operationId. For scenario=success return 200 with { status: 'accepted', providerId: 'fake_' + operationId }; for scenario=reject return 503 with { code: 'FAKE_PROVIDER_UNAVAILABLE' }; for scenario=timeout return 504 with { code: 'FAKE_PROVIDER_TIMEOUT' }; for a repeated operationId return the original result without a second record.
- 06Wire the approval server action—not browser code—to call the fake endpoint. Use operationId op_success_001 with requestId req_success_001 for success, op_reject_001 with req_reject_001 for rejection, and op_timeout_001 with req_timeout_001 for timeout. Persist each attempt before its call and then the safe providerId or error code. The UI must show pending, succeeded, and failed/retryable states while keeping the local request and attempt history truthful.
- 07Trigger each scenario once with its distinct IDs. Then repeat exactly the success operation op_success_001 with the same request ID and event. Verify the repeated call returns the original provider ID without creating a second provider operation or side effect. Confirm no token appears in browser or network receipts and every scenario can be traced through its own attempt record.
Deliverable: A role matrix, denial-test evidence, secrets inventory without values, integration attempt history, and a failed-dependency recovery receipt.
Reusable artifact
Identity and integration control sheet
Review this before any external write or production credential is enabled.
IDENTITY PROVIDER: [provider and environment] USER SCOPE: [user / team / organization / global] ROLE -> ALLOWED ACTIONS: [matrix] DENIAL TESTS: [unauthenticated, wrong user, wrong role, invalid transition] SECRET NAME: [name only] | OWNER: [...] | SCOPE: [...] | ROTATE BY: [...] COLLABORATOR ACCESS: [who can edit or run the project and therefore may access values] INTEGRATION: [service and narrow purpose] ACCESS: [account, scopes, read/write, environment] OPERATION ID: [generation and uniqueness] FAILURE POLICY: [timeout, retry, reconcile, manual recovery] AUDIT: [actor, local record, provider ID, timestamps, result] STOP GATE: No real-user write until [tests and approval].
Before you move on
Receipt checklist
- Unauthenticated, wrong-user, wrong-role, and invalid-transition requests are denied by the server.
- No secret value appears in source, Git diff, browser output, logs, screenshots, or receipts.
- Every project collaborator with runtime access is intentionally trusted for the development credentials, or access is removed before adding them.
- A successful external action can be traced from local operation ID to attempt record and safe provider reference.
- The fake provider's success, reject, timeout, and duplicate-operation scenarios produce deterministic, truthful local state and bounded recovery actions.
- Development and published identity/secret configuration are documented separately.
Primary sources
