Mobile auth backend
Skill almasumdev/awesome-mobile-backend-agent-skills/.github/skills/auth/mobile-auth-backend
Server-side OAuth 2.1 + PKCE for native mobile apps -- authorization endpoint, token endpoint, refresh rotation, and device binding. Use when implementing or reviewing the auth server for mobile clients.From its SKILL.md
npx -y skills add almasumdev/awesome-mobile-backend-agent-skills --skill mobile-auth-backendAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
5.5 KB, ~1.3k tokens by cl100k_base, as published. Nobody here has run it
Mobile Auth Backend (OAuth 2.1 + PKCE)
Instructions
Native mobile apps cannot hold a confidential client secret. The only defensible interactive-login flow is OAuth 2.1 Authorization Code with PKCE, using the system browser (ASWebAuthenticationSession on iOS, Custom Tabs on Android). No embedded WebView. No resource-owner password grant.
1. Flow Overview
App generates code_verifier (random 43-128 bytes) and code_challenge = BASE64URL(SHA256(code_verifier))
App -> /authorize?response_type=code
&client_id=mobile
&redirect_uri=com.example.app:/oauth/callback
&code_challenge=<b64url>
&code_challenge_method=S256
&state=<random>
&scope=openid profile offline_access
User signs in in system browser -> redirect back with ?code=... &state=...
App -> POST /token (code, code_verifier, redirect_uri, client_id)
-> { access_token, refresh_token, expires_in, id_token? }
2. Authorization Endpoint
Register each mobile client with:
client_id(public, no secret).- Allowed redirect URIs: universal links (
https://app.example.com/oauth/callback) are preferred over custom schemes where possible. Custom schemes (com.example.app:/oauth/callback) are allowed. - Allowed scopes and response types (
codeonly). - PKCE required; reject
code_challenge_method=plain.
Validate redirect_uri with exact string match. Never prefix-match.
3. Token Endpoint
// Node/TS (Express-ish pseudocode)
app.post("/oauth/token", async (req, res) => {
const { grant_type, code, code_verifier, redirect_uri, client_id, refresh_token } = req.body;
if (grant_type === "authorization_code") {
const entry = await codeStore.consume(code); // single use
if (!entry || entry.clientId !== client_id || entry.redirectUri !== redirect_uri) {
return res.status(400).json({ error: "invalid_grant" });
}
const challenge = base64url(sha256(code_verifier));
if (challenge !== entry.codeChallenge) {
return res.status(400).json({ error: "invalid_grant" });
}
return res.json(await issueTokens(entry.userId, client_id, entry.deviceId));
}
if (grant_type === "refresh_token") {
return res.json(await rotateRefresh(refresh_token, client_id));
}
return res.status(400).json({ error: "unsupported_grant_type" });
});
Access tokens are short-lived (5–15 minutes). Refresh tokens are long-lived but rotated on every use (see next section).
4. Refresh Rotation and Replay Detection
Every refresh request returns a new refresh token and invalidates the previous one. Store refresh tokens hashed (bcrypt/argon2 is overkill here; HMAC-SHA256 with a server-side pepper is standard) and track a session family id.
session_family_id -> (user_id, device_id, created_at)
refresh_token_hash -> (session_family_id, issued_at, rotated_at?, revoked?)
On refresh:
- Look up the token by hash.
- If already rotated or revoked -> treat as replay attack: revoke the entire
session_family_idand returninvalid_grant. Alert. - Otherwise, mark the old token rotated, issue a new pair bound to the same family.
This is RFC 6749 / OAuth 2.1 "refresh token rotation with reuse detection".
5. Device Binding
Each login creates a device_id (stable random on first launch, stored in Keychain / EncryptedSharedPreferences). Bind the session family to that device. Where the platform supports it, strengthen with:
- iOS: DeviceCheck / App Attest assertion embedded in the token request.
- Android: Play Integrity API verdict.
Reject token exchanges whose attestation does not match the registered device.
6. Access Tokens
Prefer short-lived JWTs signed with RS256 / EdDSA so resource services can verify without calling the auth server. Claims:
{
"iss": "https://auth.example.com",
"sub": "user_01HX7...",
"aud": "api",
"exp": 1713523200,
"iat": 1713522600,
"scope": "profile feed",
"device_id": "dev_01HX7...",
"sid": "sess_01HX7..."
}
Publish JWKS at /.well-known/jwks.json and rotate keys every 90 days with overlap.
7. Logout and Revocation
POST /oauth/revokewith a refresh token: revoke the session family.POST /oauth/logoutinvalidates the current session family and returns 204.- Access tokens are not individually revocable; short expiry is the mitigation. For sensitive actions (password change, device remove), publish to a revocation list (short-lived Redis set) that resource servers check.
8. Rate Limiting and Abuse
- Rate-limit
/tokenperclient_id+ IP and perdevice_id. - Lockout
/authorizecredential checks per account and per IP with exponential backoff. - Log all failed refresh rotations; repeated failures trigger session-family revocation.
Checklist
- PKCE with S256 required; plain rejected.
- Redirect URIs exact-matched; universal links preferred.
- Access tokens short-lived (≤ 15 min), JWT signed with asymmetric key.
- Refresh tokens rotated on every use with replay detection and family revocation.
- Sessions bound to
device_id; attestation verified where available. -
/revokeand/logoutimplemented; short-lived denylist for urgent revokes. - JWKS endpoint with 90-day key rotation.
- Rate limits on
/tokenand/authorizewith abuse alerting.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.