Skip to content
Agentmart
Submit a tool

auth-and-secure-storage

Builds sign-in - tokens in expo-secure-store, shared refresh, protected routes, OAuth PKCE, Apple sign-in.

Skill by zakariaf

IndexedMIT★ 11
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/zakariaf-auth-and-secure-storage

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Builds sign-in - tokens in expo-secure-store, shared refresh, protected routes, OAuth PKCE, Apple sign-in. Use when adding login, tokens, 401 retries, logout or account deletion. Not for API calls (networking-and-server-state).

Use cases: backend, security

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md and code. Checked Oct 26, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found
Runs as
Node.js package

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/auth-and-secure-storage

What's inside

Skill name
auth-and-secure-storage
Description
Builds sign-in - tokens in expo-secure-store, shared refresh, protected routes, OAuth PKCE, Apple sign-in. Use when adding login, tokens, 401 retries, logout or account deletion. Not for API calls (networking-and-server-state).
License
MIT
Instructions
212 lines in SKILL.md · view on GitHub

Before installing

  • Ships executable scripts

Files (97)

  • SKILL.md
  • shared.json · assets/
  • app-stack.tsx · examples/
  • auth-mutations.ts · examples/
  • authorized-api-client.ts · examples/
  • create-app-session.ts · examples/
  • create-device-services.ts · examples/
  • oauth-config.ts · examples/
  • oauth-redirect-route.tsx · examples/
  • root-stack.tsx · examples/
  • server-refresher.ts · examples/
  • services-context.tsx · examples/
  • sign-in-route.tsx · examples/
  • sign-in-screen.tsx · examples/
  • use-apple-sign-in.ts · examples/
  • use-delete-account-model.ts · examples/
  • use-oauth-redirect.ts · examples/
  • use-sign-in-model.ts · examples/
  • adopting-in-an-app.md · references/
  • checker-rules.md · references/
  • oauth-and-apple.md · references/
  • protected-routes.md · references/
  • secure-storage.md · references/
  • session-lifecycle.md · references/
  • check-auth.mjs · scripts/
  • check-lib.mjs · scripts/
  • auth-rules.mjs · scripts/lib/
  • auth-source.mjs · scripts/lib/
  • source-scan.mjs · scripts/lib/
  • selftest.mjs · scripts/
  • auth.json · templates/contracts/
  • auth.schema.json · templates/contracts/
  • id-token-claims.test.ts · templates/src/core/session/
  • id-token-claims.ts · templates/src/core/session/
  • token-set.test.ts · templates/src/core/session/
  • token-set.ts · templates/src/core/session/
  • apple-sign-in-port.ts · templates/src/services/apple-sign-in/
  • expo-apple-sign-in-adapter.test.tsx · templates/src/services/apple-sign-in/
  • expo-apple-sign-in-adapter.tsx · templates/src/services/apple-sign-in/
  • fake-apple-sign-in.test.tsx · templates/src/services/apple-sign-in/
  • fake-apple-sign-in.tsx · templates/src/services/apple-sign-in/
  • fake-install-marker.test.ts · templates/src/services/install-marker/
  • fake-install-marker.ts · templates/src/services/install-marker/
  • install-marker-port.ts · templates/src/services/install-marker/
  • kv-install-marker-adapter.test.ts · templates/src/services/install-marker/
  • kv-install-marker-adapter.ts · templates/src/services/install-marker/
  • auth-session-oauth-adapter.test.ts · templates/src/services/oauth/
  • auth-session-oauth-adapter.ts · templates/src/services/oauth/
  • fake-oauth.test.ts · templates/src/services/oauth/
  • fake-oauth.ts · templates/src/services/oauth/
  • oauth-port.ts · templates/src/services/oauth/
  • expo-secure-store-adapter.test.ts · templates/src/services/secure-storage/
  • expo-secure-store-adapter.ts · templates/src/services/secure-storage/
  • fake-secure-storage.test.ts · templates/src/services/secure-storage/
  • fake-secure-storage.ts · templates/src/services/secure-storage/
  • secure-key-names.test.ts · templates/src/services/secure-storage/
  • secure-key-names.ts · templates/src/services/secure-storage/
  • secure-storage-port.ts · templates/src/services/secure-storage/
  • send-with-session.test.ts · templates/src/services/session/
  • send-with-session.ts · templates/src/services/session/
  • …and 37 more

SKILL.md (first lines)

# Auth and secure storage

Makes sign-in safe and complete on iOS and Android: tokens live only in the Keychain or Keystore behind
one port, one session manager restores, refreshes and ends the session without races, route groups open
and close with it, OAuth runs the code flow with PKCE, Sign in with Apple uses a nonce, and sign-out
leaves nothing of the last person behind. A zero-dependency checker proves the structure; Jest tests
prove the behaviour.

## Rules that must hold

1. **Tokens live only in expo-secure-store, behind `SecureStoragePort`.** One adapter imports the SDK
   and passes one frozen options object (`keychainAccessible: AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY`) to
   every async call; never `getItem`/`setItem`, never `ALWAYS`, no `requireAuthentication` on session
   tokens. Why: a read with other options finds nothing; `WHEN_UNLOCKED` breaks background refresh;
   biometrics prompt on every Android read and a new fingerprint signs people out.
   (`vendor-import`, `secure-store-call`)
2. **A credential appears nowhere else.** Not in SQLite, kv-store, AsyncStorage, MMKV or files, not in a
   Zustand store (not even a bare `token` field), not in a log, crash report or analytics event, not in
   a TanStack mutation (its cache keeps variables and data), not in a URL, never in `EXPO_PUBLIC_*`.
   Why: none of those is encrypted with a hardware key, and logs, backups and URLs travel.
   (`token-storage`, `token-in-state`, `token-in-log`, `token-in-url`; these four run in every app)
3. **Every secret key is declared once in `SECURE_KEYS`; wipes loop over `ALL_SECURE_KEYS`.** Why: a
   key typed by hand somewhere survives sign-out and the fresh-install wipe. (`secure-keys`)
4. **A fresh install wipes what the Keychain kept.** An install marker in the app sandbox
   (kv-store) is checked at restore: missing means wipe every key, then mark; unreadable means never
   wipe. Why: the iOS Keychain outlives an uninstall, so a reinstall would sign the previous person
   back in. (`fresh-install-wipe`)
5. **Restore never guesses, and never waits forever.** A read that fails in the background publishes
   `unavailable` and is retried in the foreground; one that fails in the foreground (read twice) signs
   out with reason `storage-unavailable` and keeps the values; an unreadable session signs out but is
   kept. The restore is app-startup-and-bootstrap's blocking startup-gate task `restoreSessionTask`
   under its `deadlineMs` (that skill owns the one splash hold and release; auth calls no
   `SplashScreen` function); a miss opens only a "checking your session" route, never a `null` frame.
   Why: a locked phone or a parser bug must not destroy a valid session, and a broken Keystore must not
   keep the app checking so long that the person can never reach sign-in. (session-manager,
   session-context and session-guards tests)
6. **Persist, then publish; a sign-out always wins.** Sign-in writes the refresh token first, then the
   rest, and only then publishes `signed-in`; every write and wipe runs through one queue, and a
   generation number drops work started before a sign-out. Why: the reverse order shows the app and
   loses the session at the next launch; a late refresh would sign a signed-out person back in.
   (session-manager tests)
7. **One token issuer, one refresh at a time, owned by the session manager.** Every sign-in method
   ends in tokens from the issuer whose `TokenRefresher` the session holds: with an app server (Apple
   needs one) each method is exchanged there and the refresher is `createServerTokenRefresher`; the
   OAuth refresher only when OAuth is the sole method and there is no server. Callers share the
   in-flight refresh; only the session manager calls the refresher. Why: a refresh token sent to the
   wrong issuer answers `invalid_grant` one token lifetime later, and two refreshes with a rotating
   refresh token look like a replay that revokes every session. (`single-flight-refresh`, tests)
8. **Only the server ends a session.** `invalid_grant` or another OAuth error ends it; no connection,
   a timeout or 5xx keep it, and an unexpired token still goes out offline; a 401 renews once
   (`sendWithSession`), a second 401 signs out. Why: a train tunnel must never sign anyone out.
   (token-set and send-with-session tests)
9. **Protected groups are guards, not navigation.** Each signed-in group has its own `_layout` and
   sits inside `<Stack.Protected guard={isSignedIn}>`; signed-out routes sit inside
   `guard={isSignedOut}`; flows switch by changing the session, never with `router.replace`; no
   `redirectTo` before SDK 58. Why: without the group layout the guard protects none of its screens
   (verified), and a deep link opens them signed out. (`protected-groups`, session-guards test)
10. **Sign-out clears everything, in two phases.** First, on `signOut('user')`, the
    `serverSignOutSteps` (push unregistration, server logout) run with the still-valid token under one
    5 s deadline; then wipe every key, publish `signed-out` (protected screens leave), run every
    `signOutSteps` entry (query cache, account stores, rows, reminders) even when one fails, and revoke
    the refresh token best effort. Both lists are named in `contracts/auth.json`. Why: a cache nobody
    cleared shows the next person this person's data, and a push registration nobody removed sends
    them this person's notifications; after the wipe no server call can be authorized. (`sign-out-steps`)
11. **OAuth is the authorization code flow with PKCE (S256) in an ephemeral system browser.** No
    implicit flow, no WebView, no client secret, the `code_verifier` passed to `exchangeCodeAsync`
    (expo-auth-session does not add it). Why: anything else leaks tokens or codes, and a shared browser
    cookie signs the last person straight back in. (`oauth-pkce`, `oauth-ephemeral`)
12. **Sign in with Apple is iOS-only, configured, and nonced.** `usesAppleSignIn` (or the plugin), a
    fresh nonce hashed with SHA-256, `ERR_REQUEST_CANCELED` silent, the credential verified by the
    app's server, the name and email saved on the first sign-in, a revoked credential signing out,
    Apple's own button. Offer it (or an equal private login) whenever a third-party login sets up the
    account (App Store guideline 4.8). (`apple-sign-in`)
13. **Android backups leave the secure store out.** `['expo-secure-store', { faceIDPermission: false }]`
    in `app.config.ts`, or the app's own rules named in the contract. Why: prebuild does not apply this
    plugin by itself, and a restored copy cannot be decrypted on the new phone. (`secure-store-plugin`)
14. **An app that creates accounts deletes them inside the app.** A route in the signed-in group, the
    server deletes (and revokes Apple tokens), then `signOut('account-deleted')`. Why: App Store
    guideline 5.1.1(v) and Google Play (which also wants a web link) require it. (`account-deletion`)

## Workflow

1. Read `references/session-lifecycle.md` (the states, races and sign-out order) and
   `references/secure-storage.md` (platform behaviour). For the provider read
   `references/oauth-and-apple.md`; for routes `references/protected-routes.md`.
2. Install the packages and edit `app.config.ts` (`references/adopting-in-an-app.md` sections 1-2):
   the expo-secure-store plugin with `faceIDPermission: false`, Apple's plugin, a scheme per variant.
3. Copy every file under `templates/` to the path its first line names, tests included; the contract
   and schema land in `contracts/`.
4. Add the four ports to the composition root and its test, and record the SDKs in
   `contracts/architecture.json` (`examples/create-device-services.ts`, `examples/services-context.tsx`,
   adopting-in-an-app.md sections 4-5).
5. Read the provider settings in `src/config/oauth-config.ts` (`examples/oauth-config.ts`); register
   `<scheme>://oauth-redirect` for each variant with the provider (Google refuses custom schemes on
   Android: `references/oauth-and-apple.md` section 3). Stop and ask the developer for the
   issuer, client id and Apple team setup: they are outward-facing accounts.
6. Pick the one token issuer (rule 7): with a server, every method exchanges there and the session gets
   `examples/server-refresher.ts`. Create the session once (`examples/create-app-session.ts`) in
   `AppRoot`, render `SessionProvider` in
   `RootStack` (`examples/root-stack.tsx`), and guard the Stack (`examples/app-stack.tsx`); give each
   protected group its own `_layout.tsx`.
7. Build sign-in as a feature: `examples/use-sign-in-model.ts`, `examples/sign-in-screen.tsx`,
   `examples/sign-in-route.tsx`, the Android redirect route (`examples/oauth-redirect-route.tsx`,
   `examples/use-oauth-redirect.ts`), and on iOS `examples/use-apple-sign-in.ts` over
   `examples/auth-mutations.ts`.
8. Send requests to the app's server through `examples/authorized-api-client.ts` (`sessionHeaders` and
   `sendWithSession`), pass every per-account cache as a sign-out step and every server call that needs
   the session (push unregistration) as a server sign-out step; list both in `contracts/auth.json`.
9. When the app creates accounts, build deletion (`examples/use-delete-account-model.ts`) and name the
   route in the contract. Test it against a staging server only, never production.
10. Write a test for every example you adopted (`createSessionTestKit` keeps them short), and a router
    test for the app's own AppStack (from `session-guards.test.tsx`: a signed-out deep link lands on
    sign-in; the checker cannot see a guard bound to the wrong boolean), run
    `npx jest --ci` (the templates' tests cover rules 5-8, 10 and 9's behaviour), then the checker:
    `node ${CLAUDE_SKILL_DIR}/scripts/check-auth.mjs .`; fix and rerun until `RESULT: PASS`. Look up a
    rule in `references/checker-rules.md`.
11. Hand the device checklist (`references/oauth-and-apple.md` section 9) to a human for one named iOS
    device and one named Android device before release.

From zakariaf/ReactNative-Skills, MIT.

More from zakariaf/ReactNative-Skills

Signals

Stars
11
Forks
2
Open issues
0
Last commit (this folder)
Oct 17, 2026 (12 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from zakariaf

Cite this page

auth-and-secure-storage skill. Agent Mart. https://agentmart.dev/skills/zakariaf-auth-and-secure-storage (last updated 2026-10-24)