Expo + Express monorepo that showcases a full OAuth 2.0 flow against Koompi for both web and mobile.
- Single source of truth – the backend owns every credential, redirect, and session decision.
- Expo-first DX – share UI, navigation, and auth logic across native apps while still shipping to the web.
- Monorepo ergonomics – pnpm + Turborepo wire together the backend, mobile, and shared packages with zero manual linking.
- Real OAuth plumbing – deep-link capable mobile login, web callback page, and token exchange flows that mirror production deployments.
- Backend (
apps/backend) – Node.js + Express + TypeScript API with OAuth endpoints, MongoDB wiring, and JWT session issuance. - Mobile (
apps/mobile) – Expo Router app with native deep linking (example://oauth/callback), secure token storage, and starter wallet UI. - Web (
apps/web) – React web client that mirrors the same login flow via Koompi and shares UI primitives. - Shared packages – eslint config, UI kit, feature modules, and an axios-based HTTP client so every surface behaves consistently.
apps/
backend/ # Express API, OAuth controller/service, Mongo config
mobile/ # Expo Router app, auth hook, wallet views, deep linking config
web/ # React SPA consuming the OAuth session
packages/
eslint-config/ # Shared lint rules
feature-home/ # Example shared feature for mobile/web
http-client/ # Axios helpers with token attachment
ui/ # Reusable RN components
- Client bootstraps – mobile/web calls
GET /api/oauth/loginfor provider metadata (scopes, authorize URL, PKCE code verifier if used). - User authorization – client opens the returned Koompi authorize URL.
- Callback ingress – Koompi redirects to the shared backend callback (
/api/oauth/callback) withcode+stateand optionalplatformquery. - Token exchange – backend swaps the code for access token + user profile via Koompi OAuth endpoints.
- Session minting – backend normalizes the Koompi profile, persists/updates a Mongo user, and signs an app JWT.
- Callback egress
- Web → redirects to
config.frontend.url + /oauth/callback?token=.... - Mobile → deep links to
example://oauth/callback?token=....
- Web → redirects to
- Client storage – mobile uses
useAuth+ secure store, web stores the JWT via its auth context.
The entire flow (diagrams & troubleshooting) lives in docs/KOOMPI_OAUTH_INTEGRATION.md.
All secrets live in apps/backend/.env:
MONGODB_URI=mongodb://localhost:27017/example
JWT_SECRET=super-secret
SESSION_EXPIRY_HOURS=12
# Koompi OAuth credentials (from dashboard)
KOOMPI_CLIENT_ID=pk_local_...
KOOMPI_CLIENT_SECRET=sk_local_...
KOOMPI_REDIRECT_URI=http://localhost:4000/api/oauth/callback
KOOMPI_MOBILE_REDIRECT_URI=https://YOUR-NGROK-ID.ngrok-free.app/api/oauth/callback?platform=mobileClients only need to know how to reach the backend:
- Mobile – uses
process.env.EXPO_PUBLIC_BACKEND_URL(defaults tohttp://localhost:4000). - Web – configure
VITE_API_BASE_URLinapps/web/.env.local.
🔁 Use Ngrok (or Cloudflare Tunnel) to expose the backend whenever you test the mobile flow on a physical device. Update
KOOMPI_MOBILE_REDIRECT_URIto match the tunnel URL every time it changes.
pnpm install
pnpm dev # runs backend, mobile, and web watchers via Turborepopnpm dev:mobile # Expo + Metro for the mobile app
pnpm dev --filter @example/backend # backend only
pnpm dev --filter @example/web # Vite dev server- Update
apps/mobile/app.jsonif you change the scheme (example). - Launch Expo Go or a dev client:
pnpm --filter @example/mobile start. - Authenticate: backend redirects through Ngrok back into the app via
example://oauth/callback.
pnpm build # builds every package/app with caching
pnpm lint # eslint across backend, mobile, web, shared packages
pnpm test # run workspace tests (Jest)apps/backend/src/routes/oauthRoutes.ts– HTTP surface for/api/oauth/loginand/api/oauth/callback.apps/backend/src/services/oauthService.ts– exchanges codes, generates redirect URLs, maps Koompi profile → Example user.apps/mobile/hooks/useAuth.ts– encapsulates login/logout, token parsing, error handling, and deep-link listeners.apps/web/src/context/AuthContext.tsx– web session provider built on the same API responses.packages/http-client/src/index.ts– axios instance + interceptor to inject JWTs from secure storage/local storage.
| Symptom | Likely cause | Fix |
|---|---|---|
| "Redirect URI not authorized" | Ngrok URL or localhost mismatch | Re-enter the exact callback in the Koompi dashboard + backend .env |
| App never re-opens after login | Deep-link scheme mismatch | Ensure app.json scheme matches REDIRECT_SCHEME in useAuth.ts |
Invalid client from Koompi |
Wrong KOOMPI_CLIENT_ID/SECRET |
Copy the latest credentials into .env, restart backend |
| Web callback shows error | Missing VITE_API_BASE_URL |
Create apps/web/.env.local pointing to backend |
| Mobile stuck on spinner | No token in deep link | Inspect Ngrok logs, confirm backend is hitting /api/oauth/callback?platform=mobile |
docs/KOOMPI_OAUTH_INTEGRATION.md– deep dive into the OAuth flow.apps/backend/README.md,apps/mobile/README.md&apps/web/README.md– surface-specific setup notes.
With ❤️ from the KOOMPI team. Happy building!