rakomi_flutter 0.1.0
rakomi_flutter: ^0.1.0 copied to clipboard
Rakomi Flutter SDK — RFC 8252-compliant OAuth/PKCE authentication for Flutter apps targeting iOS, Android and Flutter Web. Platform-native secure storage, biometric unlock, MFA (TOTP), offline-toleran [...]
rakomi_flutter #
The official Rakomi SDK for Flutter — RFC 8252-compliant OAuth/PKCE, platform-native secure storage, biometric unlock, MFA (TOTP), offline-tolerant JWKS verification. Targets iOS, Android, and Flutter Web.
Quickstart (<15 lines) #
import 'package:flutter/material.dart';
import 'package:rakomi_flutter/rakomi_flutter.dart';
void main() => runApp(MaterialApp(home: RakomiProvider(
publishableKey: 'pk_live_…',
redirectUri: 'myapp://callback',
child: RakomiAuthGate(
signedIn: (ctx, user) => RakomiUserAvatar(),
signedOut: (ctx) => RakomiSignInForm(
providers: [SocialProvider.google, SocialProvider.apple],
),
),
)));
What this SDK gives you #
- EU-based operator: Rakomi is operated by CRE8EVE Sp. z o.o., incorporated in Poland. Primary infrastructure and database hosting are in the EU. Some optional, tenant-enabled integrations involve processing outside the EU; the full sub-processor list, the processing locations and the transfer safeguards relied upon are set out in the Data Processing Agreement, which is the authoritative source — this README makes no transfer determination.
- Multi-tenant by design: the tenant boundary is enforced at the JWT issuer level, with row-level-security-backed storage at the API. A single set of credentials serves many tenants; no per-tenant project is required.
- Open standards: OAuth 2.0, OIDC, PKCE S256, JWT/JWKS, RS256 — no proprietary token format, no cookies, no session affinity.
- Documented security posture: coordinated vulnerability disclosure via
security@rakomi.com(seeSECURITY.md), and vulnerability records kept under 10-year retention.
OIDC vs OAuth 2.0 in Rakomi #
The SDK consumes OAuth 2.0 access tokens (RS256-signed JWTs). id_token
handling for full OIDC compliance is planned and does not ship today.
Tenant scope vs OAuth scopes #
Tenant binding is encoded in the JWT claim tenant_id, NOT in OAuth
scope strings. Each tenant has its own publishable key (which derives the
storage namespace and the issuer URL).
Why not flutter_appauth underneath? #
flutter_appauth is iOS+Android only and has no Web target. We use
flutter_web_auth_2 directly because it provides a single API across
iOS / Android / Web, and the system browser semantics are identical
across the three platforms (RFC 8252 / popup postMessage).
OAuth 2.1 readiness #
- PKCE-mandatory (S256). No plain fallback.
- No implicit flow. No password-grant for new clients.
- Aligned with
draft-ietf-oauth-v2-1.
Privacy considerations (RFC 6973) #
- The SDK does NOT log tokens.
- The SDK does NOT collect device telemetry by default.
- The SDK does NOT issue cookies.
Token lifetimes #
- Access token: ~15 min (server-side configurable, see API canonical docs).
- Refresh token: ~7 d (server-side configurable).
- Single in-flight refresh dedup via Dart
Completer— never two concurrent refreshes for the same provider tree.
No cookies / no CSRF in SDK #
Bearer tokens only. The SDK adds Authorization: Bearer <access> headers and
nothing else. No cookies, no CSRF defense needed at the SDK layer.
Custom URL scheme caveats #
On Android, custom URL schemes are first-come-first-serve at the OS level — a malicious sibling app can register the same scheme and intercept the OAuth callback. The recommended remediation is Universal Links (Android App Links). This SDK currently supports custom schemes only; Universal Links support is planned.
Provisioning & code-signing #
- iOS: register your scheme in
Info.plist > CFBundleURLTypes. For Universal Links, see Apple Associated Domains. - Android: register your scheme in
AndroidManifest.xml > intent-filter. For Android App Links, see Android App Links.
iOS Info.plist snippet #
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>your.app.bundle.id</string>
<key>CFBundleURLSchemes</key>
<array>
<!-- Replace `myapp` with the scheme you passed to RakomiProvider(redirectUri:). -->
<string>myapp</string>
</array>
</dict>
</array>
Android AndroidManifest.xml snippet #
<activity android:name=".MainActivity" android:launchMode="singleTask">
<intent-filter android:autoVerify="false">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Replace `myapp` with the scheme you passed to RakomiProvider(redirectUri:). -->
<data android:scheme="myapp" android:host="callback" />
</intent-filter>
</activity>
Publishable-key acquisition #
Sign in to your tenant dashboard at https://dashboard.rakomi.com → API keys
panel → Create publishable key. Copy the pk_live_… (or pk_test_…)
prefix into RakomiProvider(publishableKey:).
Threat model #
The SDK defends against the OWASP MASVS v2 V2 (storage) / V3 (cryptography) / V4 (authentication) classes by construction. Residual risks NOT mitigated by the SDK:
- Rooted/jailbroken device: Keychain/Keystore extraction is possible.
Mitigation is consumer-app responsibility (e.g.
freerasp/RootBeer). - Device PIN bypass: on a stolen device with no passcode set, stored
credentials remain readable once the device has been unlocked at least once.
The SDK surfaces
BiometricUnavailable(reason: 'no_device_passcode')so the application can react. Credentials are stored device-only and are therefore NOT carried into an iCloud or iTunes backup. - Screen-overlay attack (Android clickjacking): consumer apps SHOULD set
WindowManager.LayoutParams.FLAG_SECUREon the auth Activity. - Keylogger: outside the SDK's threat model — biometric paths bypass password input on supported platforms.
- App-switcher screenshot leak: consumer-app responsibility — set
FLAG_SECURE(Android) /UIWindow.privacyOverlay(iOS) when authenticated state is visible. - MITM on managed networks: optional
RakomiProvider(certificateFingerprints:)pins server certificates on mobile (Web ignored — browser TLS).
Transport security #
RakomiProvider(apiBaseUrl:) requires https://. Exactly one exception exists,
for local development, and it is enforced in the constructor: an http:// URL
is accepted only when the host is exactly localhost, or is written as four
dot-separated numbers beginning 127. or 10.. Any other http:// URL is
rejected with an ArgumentError before the provider is built.
⚠ 10.0.0.0/8 is a private LAN range, not loopback — traffic to it leaves
the device. Treat that exception as a development convenience only, and never
point a production build at a plaintext http:// address.
Optional certificate pinning (RakomiProvider(certificateFingerprints:))
applies to https:// requests to non-loopback hosts on iOS and Android; on Web
it is ignored, because transport security is the browser's.
Web security considerations #
flutter_secure_storage v10 on Flutter Web stores the refresh token in
localStorage with AES-GCM encryption (key derived from a per-origin random
secret in IndexedDB). This is NOT OS-level secure storage — a successful
XSS in your app reads the token. Mitigations:
- Content Security Policy with
script-src 'self'(no inline JS). Trusted Typesif your bundler emits sinks.- Treat Web differently: shorter token lifetimes, more aggressive refresh.
Compliance posture #
- CRA (Reg. 2024/2847): coordinated vulnerability disclosure via
security@rakomi.com. The disclosure process, the timelines we commit to and the record-keeping are described inSECURITY.md. - GDPR Art. 17 (right to erasure):
signOut()clears local + this tenant's JWKS cache; fresh-install detection PURGES Keychain residue. - GDPR Art. 25 (privacy by design): non-blocking mount, no telemetry, no token logging, secure-storage namespacing.
- GDPR Art. 32 (technical measures): Keychain/EncryptedSharedPreferences
encryption-at-rest; RS256; CSPRNG; HTTPS required for the API base URL, with
a development-only
http://exception (see "Transport security" below); redacted error strings. - NIS2 (Reg. 2022/2555): coordinated vulnerability disclosure — see
SECURITY.md. - PSD2 / PSD3: the SDK can carry a TOTP factor (possession) and a biometric factor (inherence), when the API operator configures factor binding. Whether a given deployment satisfies strong customer authentication is a determination for the operator and their supervisor — the SDK does not make it and certifies nothing.
- NIST SP 800-63B: MAY meet AAL2 when the API operator configures factor binding. The SDK does not certify AAL2.
- EUDI Wallet (Reg. 2024/1183): a
SocialProvider.eudiWalletenum value is reserved as a forward-compatibility hook. No wallet integration ships today.
Audit logging #
The SDK emits 32 distinct AuthEvent types via the broadcast Stream
RakomiAuth.instance.events. Consumer apps are responsible for forwarding to
their audit log. The SDK does NOT auto-forward to any sink (CRA + GDPR Art. 30).
Audit-significant events (recommended forwarding for SOC 2 / ISO 27001):
signedIn/signedOut— recorduserId + tenantId + ts.tokenReceived/refreshSucceeded— recorduserId + sessionId + ts.signInFailed— recordemail_hash + ip_hash + reason + ts.clockSkewDetected/biometricFailed/deepLinkReceived— security-relevant; forward to SIEM.
EUDI Wallet roadmap #
The EU Digital Identity Wallet mandate begins end-of-2026 (Reg. 2024/1183
implementing acts). When the bridge endpoint lands at the API, the
SocialProvider.eudiWallet enum value will be wired without a major version
bump. Consumer-side switch statements over SocialProvider MUST use a
default: clause to allow this additive enum extension.
Stability policy #
See CHANGELOG.md § "Stability policy". TL;DR: 0.x adapter changes
additive-only; breaking changes go to 0.(x+1).0 with MIGRATION.md entry.
Publisher webhooks #
verifyPublisherWebhook verifies Rakomi publisher-app webhook deliveries (Standard Webhooks
HMAC-SHA256, replay defence, key-rotation tolerance). See the canonical
publisher-webhook receiver contract.
License #
MIT — see LICENSE.