Guides

Custom OAuth2 Flow

Wire up any OAuth 2.0 identity provider Authyra doesn't ship a prebuilt class for.

GoogleProvider and GitHubProvider are OAuth2Provider preconfigured for one identity provider. For anything else, LinkedIn, Spotify, your company's own OIDC provider, configure OAuth2Provider directly. Full field reference: OAuth2 Provider →. (Sign in with Apple is a separate case, see Apple Provider →: it uses a native SDK, not this browser-based flow.)


1. Find the three endpoints and your scopes

Every OAuth2/OIDC provider publishes an authorization endpoint, a token endpoint, and (for OIDC) a userinfo endpoint. Check the provider's developer docs, or its /.well-known/openid-configuration if it's OIDC-compliant.

2. Configure OAuth2Provider

lib/auth/linkedin_provider.dart
import 'package:authyra_flutter/authyra_flutter.dart';

final linkedInProvider = OAuth2Provider(
  config: OAuth2Config(
    providerName: 'linkedin',
    clientId: 'YOUR_LINKEDIN_CLIENT_ID',
    authorizationEndpoint: 'https://www.linkedin.com/oauth/v2/authorization',
    tokenEndpoint: 'https://www.linkedin.com/oauth/v2/accessToken',
    userInfoEndpoint: 'https://api.linkedin.com/v2/userinfo',
    redirectUri: 'myapp://auth/callback',
    scopes: ['openid', 'profile', 'email'],
    userExtractor: (json) => AuthUser(
      id: json['sub'] as String,
      email: json['email'] as String?,
      name: json['name'] as String?,
      avatarUrl: json['picture'] as String?,
    ),
  ),
);

userExtractor maps whatever JSON the userinfo endpoint returns into an AuthUser. This is provider-specific, read the response shape from the provider's docs.

No per-provider registration needed, each sign-in attempt registers its own CSRF state with OAuth2CallbackHandler for routing:

lib/main.dart
Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  AppLinks().uriLinkStream.listen(OAuth2CallbackHandler.handleCallback);

  await Authyra.initialize(
    client: AuthyraClient(
      providers: [linkedInProvider],
      storage: SecureAuthStorage(),
    ),
  );

  runApp(const MyApp());
}

Full platform (Android/iOS manifest) setup: Deep Links →.

4. Sign in

try {
  final user = await Authyra.instance.signIn('linkedin');
} on AuthenticationCancelledException {
  // user closed the browser
} on AuthenticationFailedException catch (e) {
  showError('LinkedIn sign-in failed: $e');
}

Client secret: PKCE vs confidential

Most mobile/desktop apps should use PKCE (usePkce: true, the default) and omit clientSecret entirely, a secret embedded in the app binary isn't actually secret. If your provider requires a confidential client (like GitHub, which doesn't support PKCE), either accept that trade-off or delegate the exchange to your backend with ProxyOAuthProvider →, which keeps the secret server-side.

Provider-specific quirks

Extra query params some providers require go in additionalAuthParams:

OAuth2Config(
  // ...
  additionalAuthParams: const {
    'access_type': 'offline', // Google: request a refresh token
    'prompt': 'consent',      // Google: always show the consent screen
  },
)

Overriding scopes for one sign-in call

await Authyra.instance.signIn('linkedin', params: OAuth2SignInParams(
  scopes: ['openid', 'profile'],
));
Copyright © 2026