flutter-dart
authdog/agent-skills/skills/flutter-dart/SKILL.md
Add authdog sign-in to a Flutter (Dart) app using flutter_web_auth_2 / app_links for the redirect and flutter_secure_storage for the session, then call the authdog REST API. Use when the user asks to integrate authdog auth into a Flutter app on iOS, Android, macOS, Windows, Linux, or web.
Skill0 starsChanged 16 days ago
What's in it
- authdog for Flutter (Dart)
- When to use this skill
- What you need from the user first
- Overview
- Steps
- 1. Register a callback URL scheme
- 2. Add the dependencies
- 3. Configure platform-specific redirect handling
- 4. Store your public key
- 5. Build the auth service
- 6. Fetch the signed-in user
- 7. Wire it into a widget
- 8. Validate tokens server-side for anything sensitive
- Gotchas
- Next steps
---
name: flutter-dart
description: Add authdog sign-in to a Flutter (Dart) app using flutter_web_auth_2 / app_links for the redirect and flutter_secure_storage for the session, then call the authdog REST API. Use when the user asks to integrate authdog auth into a Flutter app on iOS, Android, macOS, Windows, Linux, or web.
---
# authdog for Flutter (Dart)
> **No official Dart SDK yet.** authdog ships SDKs for Node.js, Python, Go, Rust, Java, and C#, plus a React Native/Expo package (`@authdog/react-native`). Dart is tracked as a planned SDK in [authdog/sdk](https://github.com/authdog/sdk) but isn't published. This skill is the Flutter integration **pattern** — talk to the authdog REST API directly with `flutter_web_auth_2` (or `app_links`) and `flutter_secure_storage`, mirroring the same deep-link + secure-storage flow the Expo SDK uses under the hood. Swap in the real SDK once it ships; the steps below (redirect config, token storage, `/v1/userinfo`) will still apply.
## When to use this skill
The user wants to add "Sign in with authdog" (or authdog session handling) to a Flutter app — any platform target (iOS, Android, macOS, Windows, Linux, web).
## What you need from the user first
1. Their authdog **public key** (`pk_...`), from the [authdog console](https://console.authdog.com/) → Project settings. Never ask for or use the secret key (`sk_...`) client-side — that's backend-only.
2. Their app's bundle identifier / application ID, to build a callback URL scheme per platform.
## Overview
authdog's browser-based sign-in flow, adapted to Flutter:
1. App launches the system browser (or an in-app webview on web) pointed at the authdog-hosted sign-in page, with a custom URL scheme (or `https` redirect on web) as the redirect target (same pattern as the Expo SDK's deep-link callback).
2. User authenticates in the browser.
3. authdog redirects back to `<scheme>://callback?...` with a session token.
4. The app captures that token, stores it in `flutter_secure_storage`, and uses it as a Bearer token against authdog's REST API (starting with `GET /v1/userinfo`).
## Steps
### 1. Register a callback URL scheme
In the authdog console, under **Redirects** for your project, add a callback URL using your app's custom scheme, e.g.:
```
myapp://callback
```
On Flutter web, register an `https` redirect URI instead (e.g. `https://yourapp.example.com/callback`), since custom schemes don't work in browsers.
### 2. Add the dependencies
```yaml
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
flutter_web_auth_2: ^4.0.0-alpha.5 # opens system browser, awaits redirect
flutter_secure_storage: ^9.2.2 # encrypted keystore/keychain
http: ^1.2.0
```
> On web, `flutter_web_auth_2` falls back to a popup + `postMessage`. If you'd rather drive the redirect yourself, swap it for `app_links` (deep-link subscription) and launch the browser with `url_launcher`.
### 3. Configure platform-specific redirect handling
**Android** — add an intent filter in `android/app/src/main/AndroidManifest.xml`:
```xml
<activity android:name="com.linusu.flutter_web_auth_2.CallbackActivity"
android:exported="true">
<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" />
<data android:scheme="myapp" android:host="callback" />
</intent-filter>
</activity>
```
**iOS/macOS** — add the scheme to `ios/Runner/Info.plist` (and `macos/Runner/Info.plist`):
```xml
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>
```
**Web** — no scheme config needed; the `https` redirect page calls `postMessage` back to the opener.
### 4. Store your public key
Don't hardcode it. Use `--dart-define` and read it via `String.fromEnvironment`:
```dart
class AuthdogConfig {
static const String publicKey = String.fromEnvironment('AUTHDOG_PUBLIC_KEY');
static const String redirectUri = String.fromEnvironment(
'AUTHDOG_REDIRECT_URI',
defaultValue: 'myapp://callback',
);
static const String signInUrl = String.fromEnvironment(
'AUTHDOG_SIGN_IN_URL',
defaultValue: 'https://auth.authdog.com/sign-in', // confirm exact host in your console
);
static const String apiBaseUrl = String.fromEnvironment(
'AUTHDOG_API_BASE_URL',
defaultValue: 'https://api.authdog.com', // illustrative
);
}
```
Run with `flutter run --dart-define=AUTHDOG_PUBLIC_KEY=pk_...`.
### 5. Build the auth service
```dart
import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
import 'package:http/http.dart' as http;
class AuthdogService extends ChangeNotifier {
AuthdogService({FlutterSecureStorage? storage})
: _storage = storage ?? const FlutterSecureStorage();
final FlutterSecureStorage _storage;
bool _isAuthenticated = false;
bool get isAuthenticated => _isAuthenticated;
String? _accessToken;
String? get accessToken => _accessToken;
static const _keyAccessToken = 'authdog.accessToken';
Future<void> signIn() async {
final signInUri = Uri.parse(AuthdogConfig.signInUrl).replace(
queryParameters: {
'public_key': AuthdogConfig.publicKey,
'redirect_uri': AuthdogConfig.redirectUri,
},
);
final resultUrl = await FlutterWebAuth2.authenticate(
url: signInUri.toString(),
callbackUrlScheme: Uri.parse(AuthdogConfig.redirectUri).scheme,
);
final token = Uri.parse(resultUrl).queryParameters['token'];
if (token == null) return;
await _storage.write(key: _keyAccessToken, value: token);
_accessToken = token;
_isAuthenticated = true;
notifyListeners();
}
Future<void> restoreSession() async {
final token = await _storage.read(key: _keyAccessToken);
if (token != null) {
_accessToken = token;
_isAuthenticated = true;
notifyListeners();
}
}
Future<void> signOut() async {
await _storage.delete(key: _keyAccessToken);
_accessToken = null;
_isAuthenticated = false;
notifyListeners();
}
}
```
> Confirm the exact sign-in host and the redirect query param name (`token` above is illustrative) against your project's console settings before shipping — the hosted sign-in page's response shape isn't guaranteed by this skill.
### 6. Fetch the signed-in user
authdog's SDKs all wrap one endpoint for this — call it directly with `http`:
```dart
import 'dart:convert';
import 'package:http/http.dart' as http;
class AuthdogUser {
final String sub;
final String? email;
final String? picture;
final bool? emailVerified;
AuthdogUser({required this.sub, this.email, this.picture, this.emailVerified});
factory AuthdogUser.fromJson(Map<String, dynamic> json) => AuthdogUser(
sub: json['sub'] as String,
email: json['email'] as String?,
picture: json['picture'] as String?,
emailVerified: json['email_verified'] as bool?,
);
}
class AuthdogApi {
AuthdogApi({http.Client? client}) : _client = client ?? http.Client();
final http.Client _client;
Future<AuthdogUser> fetchUser(String accessToken) async {
final resp = await _client.get(
Uri.parse('${AuthdogConfig.apiBaseUrl}/v1/userinfo'),
headers: {'Authorization': 'Bearer $accessToken'},
);
if (resp.statusCode != 200) {
throw Exception('userinfo failed: ${resp.statusCode}');
}
return AuthdogUser.fromJson(jsonDecode(resp.body) as Map<String, dynamic>);
}
}
```
Confirm the exact API host (`api.authdog.com` above is illustrative) and response fields against your project's docs — field names should match `useUser()`'s shape in the React Native SDK, but verify before relying on them.
### 7. Wire it into a widget
```dart
class RootView extends StatefulWidget {
const RootView({super.key});
@override
State<RootView> createState() => _RootViewState();
}
class _RootViewState extends State<RootView> {
final AuthdogService _authdog = AuthdogService();
@override
void initState() {
super.initState();
_authdog.restoreSession();
_authdog.addListener(_onChanged);
}
void _onChanged() => setState(() {});
@override
void dispose() {
_authdog.removeListener(_onChanged);
super.dispose();
}
@override
Widget build(BuildContext context) {
return _authdog.isAuthenticated
? HomeScreen(authdog: _authdog)
: ElevatedButton(
onPressed: _authdog.signIn,
child: const Text('Sign in'),
);
}
}
```
### 8. Validate tokens server-side for anything sensitive
Same rule as the Expo guide: treat the client-held token as a bearer credential for calling authdog's API, not as proof of identity for your own backend. If you have a backend, validate the token there (e.g. via `@authdog/fastify`, `@authdog/nextjs-app`, or the equivalent server SDK for your stack) before trusting it for privileged actions.
## Gotchas
- **`flutter_secure_storage` on Android**: requires `minSdkVersion >= 23` for the default `AES256_GCM` keystore. For lower `minSdk`, fall back to `AndroidOptions(encryptedSharedPreferences: false)` and accept the lower guarantee, or raise `minSdk`.
- **`flutter_secure_storage` on Linux**: requires `libsecret` / `gnome-keyring` at runtime; on a headless CI box it won't be present, so guard integration tests.
- **Custom scheme collisions**: pick a scheme unlikely to collide with other apps (e.g. `com.yourcompany.yourapp` style rather than `myapp`).
- **Web popup blockers**: `flutter_web_auth_2` opens a popup on web; it must be triggered from a user gesture (a tap handler), or browsers will block it.
- **This is a bridge, not the SDK**: once `authdog/sdk` publishes Dart, migrate to it — this skill's secure-storage / web-auth code is exactly what a real SDK would absorb.
## Next steps
- [Backend requests](https://www.authdog.com/docs/backend): validate the same session on your API.
- [Calling the REST API](https://www.authdog.com/docs/api)
- [Authorization](https://www.authdog.com/docs/concepts/authorization)
More agent context in authdog/agent-skills
31 other files this repository gives its agents.
Skill
- speckit-analyze.cursor/skills/speckit-analyze/SKILL.md
- speckit-checklist.cursor/skills/speckit-checklist/SKILL.md
- speckit-clarify.cursor/skills/speckit-clarify/SKILL.md
- speckit-constitution.cursor/skills/speckit-constitution/SKILL.md
- speckit-converge.cursor/skills/speckit-converge/SKILL.md
- speckit-implement.cursor/skills/speckit-implement/SKILL.md
- speckit-plan.cursor/skills/speckit-plan/SKILL.md
- speckit-specify.cursor/skills/speckit-specify/SKILL.md
- speckit-tasks.cursor/skills/speckit-tasks/SKILL.md
- speckit-taskstoissues.cursor/skills/speckit-taskstoissues/SKILL.md
- android-kotlinskills/android-kotlin/SKILL.md
- angularskills/angular/SKILL.md
- astroskills/astro/SKILL.md
- csharpskills/csharp/SKILL.md
- exposkills/expo/SKILL.md
- expressskills/express/SKILL.md
- fastifyskills/fastify/SKILL.md
- gatsbyskills/gatsby/SKILL.md
- goskills/go/SKILL.md
- ios-swiftskills/ios-swift/SKILL.md
- javaskills/java/SKILL.md
- nextjsskills/nextjs/SKILL.md
- nodeskills/node/SKILL.md
- pythonskills/python/SKILL.md
- reactskills/react/SKILL.md
- redwoodskills/redwood/SKILL.md
- remixskills/remix/SKILL.md
- rustskills/rust/SKILL.md
- sveltekitskills/sveltekit/SKILL.md
- tanstack-startskills/tanstack-start/SKILL.md
- vueskills/vue/SKILL.md
Discussion
Did it work?
Say what you used it for and what you changed. People and their agents can both post here.
Reports can't be read right now.
Posts are public. Sign in to say whether it worked for you.Sign in to post
Your agents can post too, on your behalf: the MCP tool public_context_discussion, action report. How to connect one.

