OAuth2 Provider
OAuth2Provider implements the full browser-based Authorization Code flow with optional PKCE. Use it directly for any identity provider not covered by the prebuilt subclasses (GoogleProvider, GitHubOAuth2Provider, AppleProvider).
Package: authyra_flutter
How it works
App Identity Provider
│── build authorization URL ───────────────────►│
│── open external browser ──────────────────────►│
│ │ (user authenticates)
│◄── deep-link callback (?code=…&state=…) ──────│
│── exchange code for tokens ───────────────────►│
│◄── { access_token, refresh_token, expires_in } │
│── GET /userinfo (Bearer) ─────────────────────►│
│◄── { sub, email, name, … } ───────────────────│
PKCE (RFC 7636) is enabled by default and is strongly recommended for mobile and desktop apps that cannot safely store a client secret.
Constructor
OAuth2Provider({
required OAuth2Config config,
Dio? dio,
})
| Parameter | Type | Description |
|---|---|---|
config | OAuth2Config | Endpoints, credentials, scopes, and user extractor |
dio | Dio? | Optional Dio instance — share interceptors or inject in tests |
OAuth2Config
OAuth2Config is an immutable value object that describes everything the provider needs.
OAuth2Config({
required String providerName,
required String clientId,
required String authorizationEndpoint,
required String tokenEndpoint,
required String userInfoEndpoint,
required String redirectUri,
required List<String> scopes,
required AuthUser Function(Map<String, dynamic>) userExtractor,
String? clientSecret,
bool usePkce = true,
Map<String, String> additionalAuthParams = const {},
Map<String, String> additionalTokenParams = const {},
Map<String, String>? tokenHeaders,
Map<String, String>? userInfoHeaders,
Duration timeout = const Duration(minutes: 5),
})
| Field | Default | Description |
|---|---|---|
providerName | required | Lowercase slug — used as AuthProvider.id |
clientId | required | Client ID from the identity provider |
authorizationEndpoint | required | URL the browser opens for user consent |
tokenEndpoint | required | URL to exchange the code for tokens |
userInfoEndpoint | required | URL to fetch the user profile |
redirectUri | required | Custom URI scheme the browser redirects back to |
scopes | required | OAuth 2.0 scopes to request |
userExtractor | required | Maps the userinfo JSON to an AuthUser |
clientSecret | null | Required for confidential clients; omit for PKCE flows |
usePkce | true | PKCE recommended for all public clients |
additionalAuthParams | {} | Extra query params on the authorization URL |
additionalTokenParams | {} | Extra body params on the token request |
tokenHeaders | null | Custom headers on the token request |
userInfoHeaders | null | Custom headers on the userinfo request |
timeout | 5 min | Max wait before throwing AuthenticationCancelledException |
clientSecret in a Flutter / mobile app. Use usePkce: true (default) or delegate the flow to your backend with ProxyOAuthProvider.Quick example — Discord
import 'package:authyra_flutter/authyra_flutter.dart';
final discordProvider = OAuth2Provider(
config: OAuth2Config(
providerName: 'discord',
clientId: 'YOUR_DISCORD_CLIENT_ID',
authorizationEndpoint: 'https://discord.com/oauth2/authorize',
tokenEndpoint: 'https://discord.com/api/oauth2/token',
userInfoEndpoint: 'https://discord.com/api/users/@me',
redirectUri: 'myapp://auth/callback',
scopes: ['identify', 'email'],
userExtractor: (json) => AuthUser(
id: json['id'] as String,
email: json['email'] as String?,
name: json['username'] as String?,
avatarUrl: json['avatar'] != null
? 'https://cdn.discordapp.com/avatars/${json['id']}/${json['avatar']}.png'
: null,
),
),
);
Registration and initialization
import 'package:app_links/app_links.dart';
import 'package:authyra_flutter/authyra_flutter.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// 1. Register the provider for deep-link routing
OAuth2CallbackHandler.registerProvider('myapp', discordProvider);
// 2. Forward all incoming links
AppLinks().uriLinkStream.listen(OAuth2CallbackHandler.handleCallback);
// 3. Initialize Authyra
await Authyra.initialize(
client: AuthyraClient(
providers: [discordProvider],
storage: SecureAuthStorage(),
),
);
runApp(const MyApp());
}
Deep-link platform setup
Register your custom URI scheme with each platform:
Android — android/app/src/main/AndroidManifest.xml:
<intent-filter>
<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="auth" android:pathPrefix="/callback" />
</intent-filter>
iOS — ios/Runner/Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>
Sign in
try {
final user = await Authyra.instance.signIn('discord');
print('Signed in as ${user.name}');
} on AuthenticationCancelledException {
// User closed the browser
} on AuthenticationFailedException catch (e) {
print('Sign-in failed: $e');
}
Token refresh
OAuth2Provider sets supportsRefresh: true. When the active session is within the refresh window (controlled by AuthConfig.refreshBeforeExpiry), AuthyraClient calls provider.refreshToken(refreshToken) automatically.
Override refreshToken in a subclass if the provider uses a non-standard refresh flow.
Advanced — additional auth params
Some providers require extra parameters on the authorization URL:
OAuth2Config(
// ...
additionalAuthParams: const {
'access_type': 'offline', // Google: request refresh token
'prompt': 'consent', // Google: always show consent screen
},
)
Advanced — custom scopes at sign-in time
Pass scope in the params map to override the configured scopes for a single call:
await Authyra.instance.signIn('discord', params: {
'scope': 'identify email guilds',
});
copyWith
OAuth2Config exposes copyWith for environment-specific overrides or tests:
final stagingConfig = prodConfig.copyWith(
redirectUri: 'myapp-staging://auth/callback',
);
See also
- GoogleProvider → — prebuilt Google Sign-In
- GitHubOAuth2Provider → — prebuilt GitHub OAuth
- AppleProvider → — Sign in with Apple
- ProxyOAuthProvider → — backend-delegated flow
- Flutter Setup → — OAuth2 deep-link wiring