reis-mobile 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +23 -0
- package/README.md +54 -27
- package/agents/android-architect.md +3 -3
- package/agents/android-performance-engineer.md +1 -1
- package/agents/flutter-architect.md +3 -3
- package/agents/flutter-performance-engineer.md +1 -1
- package/agents/ios-architect.md +3 -3
- package/agents/ios-performance-engineer.md +1 -1
- package/agents/kmp-architect.md +3 -3
- package/agents/kmp-performance-engineer.md +1 -1
- package/agents/lead-mobile.md +3 -0
- package/agents/mobile-release-engineer.md +4 -4
- package/agents/mobile-staff-engineer.md +1 -1
- package/agents/rn-architect.md +3 -3
- package/agents/rn-performance-engineer.md +1 -1
- package/bin/reis-mobile.mjs +24 -1
- package/commands/reis-mobile.md +7 -2
- package/commands/release.md +46 -0
- package/core/release/context.mjs +11 -0
- package/core/router/intents.mjs +15 -0
- package/package.json +1 -1
- package/skills/mobile-accessibility/SKILL.md +3 -0
- package/skills/mobile-accessibility/references/localization.md +77 -0
- package/skills/mobile-observability/SKILL.md +137 -0
- package/skills/mobile-observability/references/android.md +105 -0
- package/skills/mobile-observability/references/flutter.md +86 -0
- package/skills/mobile-observability/references/ios.md +88 -0
- package/skills/mobile-observability/references/kotlin-multiplatform.md +76 -0
- package/skills/mobile-observability/references/react-native.md +62 -0
- package/skills/mobile-offline-sync/SKILL.md +132 -0
- package/skills/mobile-offline-sync/references/android.md +123 -0
- package/skills/mobile-offline-sync/references/flutter.md +103 -0
- package/skills/mobile-offline-sync/references/ios.md +103 -0
- package/skills/mobile-offline-sync/references/kotlin-multiplatform.md +114 -0
- package/skills/mobile-offline-sync/references/react-native.md +108 -0
- package/skills/mobile-performance/SKILL.md +88 -0
- package/skills/mobile-performance/references/android.md +96 -0
- package/skills/mobile-performance/references/background.md +87 -0
- package/skills/mobile-performance/references/flutter.md +101 -0
- package/skills/mobile-performance/references/ios.md +82 -0
- package/skills/mobile-performance/references/kotlin-multiplatform.md +62 -0
- package/skills/mobile-performance/references/react-native.md +85 -0
- package/skills/mobile-release/SKILL.md +11 -22
- package/skills/mobile-release/references/billing.md +92 -0
- package/skills/mobile-release/references/readiness.md +88 -0
- package/skills/mobile-release/scripts/verdict.mjs +59 -0
- package/skills/mobile-security/SKILL.md +3 -0
- package/skills/mobile-security/references/auth.md +92 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# OAuth 2.0, OIDC and PKCE in mobile apps
|
|
2
|
+
|
|
3
|
+
Applies to every stack. A mobile app is a **public client**: it cannot keep a secret, because anything shipped in the binary can be extracted. Every rule here follows from that one fact, and the authoritative source is **RFC 8252, "OAuth 2.0 for Native Apps"**, which the major identity providers implement and enforce.
|
|
4
|
+
|
|
5
|
+
## The three rules that decide whether the flow is correct
|
|
6
|
+
|
|
7
|
+
1. **Authorization Code flow with PKCE. Nothing else.** The Implicit flow and the Resource Owner Password Credentials flow are both disallowed for native apps — implicit returns tokens in a redirect where they leak, and ROPC makes the app handle the password, defeats federated login and blocks MFA. If the code sends a username and password to a `/token` endpoint, that is the finding.
|
|
8
|
+
2. **No client secret in the app.** A "confidential" client registration used by a mobile binary is a published secret. Register the app as a public client; PKCE is what replaces the secret.
|
|
9
|
+
3. **The authorization request runs in the system browser, not in a WebView.** RFC 8252 requires an external user-agent — `ASWebAuthenticationSession` on iOS, Custom Tabs on Android — because it shares the system's session (so SSO works), shows the real URL and certificate to the user (so phishing is detectable), and keeps the app out of the credential entry. An embedded `WebView` on the login page gives the app access to the user's typed password and is rejected by most identity providers.
|
|
10
|
+
|
|
11
|
+
## PKCE, concretely
|
|
12
|
+
|
|
13
|
+
PKCE (RFC 7636) stops an attacker who intercepts the redirect — a malicious app that registered the same custom scheme, or a log that captured the URL — from exchanging the authorization code.
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
verifier = 43–128 chars from [A-Z a-z 0-9 - . _ ~], from a cryptographic RNG
|
|
17
|
+
challenge = BASE64URL( SHA256( verifier ) ) ← method "S256"
|
|
18
|
+
|
|
19
|
+
/authorize?...&code_challenge=<challenge>&code_challenge_method=S256
|
|
20
|
+
/token ...&code=<code>&code_verifier=<verifier>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
What to check in review:
|
|
24
|
+
|
|
25
|
+
- [ ] `code_challenge_method` is `S256`. The `plain` method provides no protection and exists only for constrained clients.
|
|
26
|
+
- [ ] The verifier comes from a **cryptographic** random source — `SecureRandom`, `SecRandomCopyBytes`, `Random.secure()`, `crypto.getRandomValues` — never `Math.random()`, a timestamp or a UUID v4 from a non-crypto source.
|
|
27
|
+
- [ ] A fresh verifier per authorization request, held only in memory, discarded after the exchange.
|
|
28
|
+
- [ ] The `state` parameter is present, random, and **verified** on the callback. `state` is CSRF protection and is separate from PKCE; PKCE does not replace it.
|
|
29
|
+
- [ ] With OIDC, the `nonce` is sent and checked against the `nonce` claim in the ID token.
|
|
30
|
+
|
|
31
|
+
## Redirects
|
|
32
|
+
|
|
33
|
+
The redirect URI is how the code gets back to the app, and it is the weak point.
|
|
34
|
+
|
|
35
|
+
- **Prefer verified app links**: `https` App Links on Android (with `assetlinks.json` on the domain) and Universal Links on iOS (with `apple-app-site-association`). These are the only redirect types the OS ties to a domain you own; another app cannot claim them.
|
|
36
|
+
- **Custom schemes are claimable** by any installed app. If one is used, it must be app-specific and unguessable-ish (the reverse-DNS form providers recommend, e.g. `com.example.app:/oauth2redirect`), and PKCE must be present — with PKCE, a stolen code is useless.
|
|
37
|
+
- **Loopback redirects** (`http://127.0.0.1:<port>`) are the desktop pattern and are not appropriate on mobile.
|
|
38
|
+
- Validate the callback: correct `state`, expected issuer, and no code reuse.
|
|
39
|
+
|
|
40
|
+
## Tokens
|
|
41
|
+
|
|
42
|
+
- **Storage:** the Keychain on iOS and Keystore-backed storage on Android — never `UserDefaults`, `SharedPreferences`, `AsyncStorage`, a plain file, `localStorage` in a WebView, or the JavaScript bundle. Per-stack APIs are in the platform references of this skill.
|
|
43
|
+
- **Access tokens are short-lived** (minutes to an hour). A long-lived access token is a finding by itself.
|
|
44
|
+
- **Refresh tokens must rotate.** Each refresh returns a new refresh token and invalidates the old one; a reused old token means theft, and the server must revoke the whole family. Rotation with reuse detection is the mobile standard because a public client cannot prove its identity any other way.
|
|
45
|
+
- **Never put a token in a URL**, a log line, an analytics event, a crash-report attribute or a breadcrumb — see [mobile-observability](../../mobile-observability/SKILL.md).
|
|
46
|
+
- **Validate the ID token** if the client uses its claims: signature against the provider's JWKS, `iss`, `aud`, `exp`, and `nonce`. An ID token is proof of authentication for the client; it is **not** an access token and must never be sent to your API as one.
|
|
47
|
+
- **Authorization is the backend's decision.** Claims decoded on the device decide what to render, never what is allowed. A client that reads `role: admin` from a token and unlocks a feature is a client-side authorization finding regardless of how well the token is stored.
|
|
48
|
+
- **Logout** must revoke the refresh token at the provider (`revocation_endpoint`), clear the token store, and end the browser session where the provider supports RP-initiated logout. Deleting the local copy alone leaves a valid session on the server.
|
|
49
|
+
|
|
50
|
+
## Biometrics do not authenticate to your server
|
|
51
|
+
|
|
52
|
+
A biometric prompt that returns `true` and unlocks a screen proves nothing to the backend and can be bypassed on a compromised device. Biometrics are correct when they **gate access to a key**: the token or a key that decrypts it is stored with biometric-backed access control (`kSecAccessControlBiometryCurrentSet` on iOS, `setUserAuthenticationRequired` on an Android Keystore key), so failing the prompt makes the data undecryptable rather than just hiding a screen.
|
|
53
|
+
|
|
54
|
+
Invalidate on enrolment change: adding a fingerprint or face must invalidate the key, or a coerced enrolment grants access.
|
|
55
|
+
|
|
56
|
+
## Use the platform libraries
|
|
57
|
+
|
|
58
|
+
Hand-written OAuth is where these mistakes accumulate. Prefer a maintained implementation that already does RFC 8252 correctly, and keep the verifier, `state` and browser handling out of your own code:
|
|
59
|
+
|
|
60
|
+
| Stack | Typical choice |
|
|
61
|
+
|---|---|
|
|
62
|
+
| Android | AppAuth for Android, or the identity provider's own SDK; Custom Tabs for the browser |
|
|
63
|
+
| iOS | AppAuth for iOS, or the provider's SDK; `ASWebAuthenticationSession` for the browser |
|
|
64
|
+
| Flutter | `flutter_appauth`, or the provider's Flutter SDK; secure storage for tokens |
|
|
65
|
+
| React Native / Expo | `react-native-app-auth`, or `expo-auth-session` (which implements PKCE); never a `WebView` |
|
|
66
|
+
| Kotlin Multiplatform | The host apps own the browser flow; keep only token *use* and refresh scheduling in shared code |
|
|
67
|
+
|
|
68
|
+
Check what the chosen library defaults to. Some have an option to use an embedded WebView or to disable PKCE for legacy providers — both turn a compliant flow into a non-compliant one silently.
|
|
69
|
+
|
|
70
|
+
## Review checklist
|
|
71
|
+
|
|
72
|
+
- [ ] Authorization Code + PKCE (`S256`); no implicit, no password grant.
|
|
73
|
+
- [ ] No client secret in the app, the repository, or the CI configuration that builds it.
|
|
74
|
+
- [ ] System browser (`ASWebAuthenticationSession` / Custom Tabs), not a `WebView`.
|
|
75
|
+
- [ ] Cryptographic RNG for the verifier and `state`; `state` and `nonce` verified.
|
|
76
|
+
- [ ] Redirect URI is an App Link / Universal Link, or an app-specific scheme with PKCE.
|
|
77
|
+
- [ ] Tokens in Keychain/Keystore-backed storage, short-lived access token, rotating refresh token with reuse detection.
|
|
78
|
+
- [ ] No token in logs, URLs, analytics or crash reports.
|
|
79
|
+
- [ ] ID token validated if used; never sent as an access token.
|
|
80
|
+
- [ ] Authorization enforced by the backend on every request.
|
|
81
|
+
- [ ] Logout revokes server-side.
|
|
82
|
+
- [ ] Biometrics gate a key, not a boolean; enrolment changes invalidate it.
|
|
83
|
+
|
|
84
|
+
## Official documentation
|
|
85
|
+
|
|
86
|
+
- RFC 8252, OAuth 2.0 for Native Apps: https://datatracker.ietf.org/doc/html/rfc8252
|
|
87
|
+
- RFC 7636, Proof Key for Code Exchange: https://datatracker.ietf.org/doc/html/rfc7636
|
|
88
|
+
- OAuth 2.0 Security Best Current Practice: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics
|
|
89
|
+
- OpenID Connect Core: https://openid.net/specs/openid-connect-core-1_0.html
|
|
90
|
+
- OWASP MASVS, authentication and session management: https://mas.owasp.org/MASVS/05-MASVS-AUTH/
|
|
91
|
+
- ASWebAuthenticationSession: https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession
|
|
92
|
+
- Android Custom Tabs: https://developer.chrome.com/docs/android/custom-tabs
|