Drivers
A driver runs one provider's flow and answers what the backend concluded: it never logs the app in itself, it returns a SocialAuthResult.
On this page
SocialDriver Contract#
SocialAuthManager resolves drivers by name and caches them for the lifetime of the app. The caller decides what a session, a 2FA challenge or a cancelled deletion means (see Flows).
import 'package:magic_social_auth/magic_social_auth.dart';
Future<void> useDriver(String currentPassword) async {
final SocialDriver driver = SocialAuth.driver('github');
// Sign in
await driver.signIn();
// Connect (mobile)
await driver.connect({'password': currentPassword});
// Connect (web): prepare, then open from a second tap
final Future<SocialAuthResult> Function() open = await driver.beginConnect({
'password': currentPassword,
});
// ...in the next tap handler:
await open();
}
| Method | Backend intent | Answer carries |
|---|---|---|
signIn() |
signin |
token and user, or isTwoFactor and twoFactorToken; deletionCancelled when the sign-in cancelled a scheduled deletion. |
beginConnect(proof) / connect(proof) |
connect |
connectedProvider. Needs the caller's bearer token. |
confirm() |
confirm |
confirmationToken, a single-use step-up proof. Needs the caller's bearer token. |
signOut() |
none | Ends the provider SDK's own session; a no-op by default. |
proof is the step-up field the account can give: {'password': ...}, {'code': ...} or {'confirmation_token': ...}; null for a guest, who sends none. It is minted for this one call; never cache or reuse it.
beginConnect exists for the web. A connect needs a link ticket first, which is a network request; a popup opened after an await is blocked as not user-initiated. So beginConnect does everything that needs the network and returns the call that opens the provider before its own first await. Run it from a fresh tap (an explicit "Continue to GitHub" button) and the popup opens. connect(proof) is beginConnect followed by the opener in one go, for mobile.
On the web a signIn() opens the popup in the same synchronous run as the call, so call it straight from the tap handler with no await before it.
SocialAuthResult#
| Field | Type | Set by |
|---|---|---|
token |
String? |
A completed sign-in: the Sanctum token. |
user |
Map<String, dynamic>? |
A completed sign-in: the user resource. |
isTwoFactor |
bool |
A sign-in on an account with confirmed 2FA. |
twoFactorToken |
String? |
Same: finish at auth/two-factor-challenge with it. |
deletionCancelled |
bool |
A sign-in that cancelled a scheduled account deletion. |
connectedProvider |
String? |
A connect. |
confirmationToken |
String? |
A confirm. |
AppleSignInResult extends it with givenName and familyName: Apple shares the name on the first authorisation only and never puts it in the ID token, so pass it on to a profile update or it is lost.
Built-in Drivers#
The manager picks the driver that runs on the current platform. SocialAuth.driver('google') is a GoogleDriver on iOS and Android and a RedirectDriver on the web.
GoogleDriver#
The native Google SDK (google_sign_in) on iOS and Android. The SDK's ID token is posted to auth/social/google/token, which verifies it. No nonce goes with it: the backend prohibits one for Google.
- Config:
ios_client_id(iOS only) andserver_client_id(the ID token's audience). See Configuration. - The SDK accepts one
initializeper process, so every instance shares the first one. signOut()signs the SDK out so the next sign-in offers the account picker again.- A cancelled sheet throws
SocialAuthCancelledException. Android also reports a misconfigured client (SHA-1, package name,server_client_id) as cancelled.
AppleDriver#
Sign in with Apple through the native sheet on iOS (sign_in_with_apple). Every sheet gets a fresh Nonce: Apple receives its lowercase sha256 hex and seals it into the ID token, the backend receives the raw value and checks the two match, so a token lifted from another sign-in is refused. The authorisation code goes along for the refresh token that account deletion revokes. The result is an AppleSignInResult.
RedirectDriver#
Any provider through the backend's browser flow: GitHub and Microsoft everywhere, Apple on Android and the web, Google on the web. The app mints a PKCE pair, opens <base_url>/auth/social/{provider}/redirect, receives a one-time code on the callback and trades it with the verifier at auth/social/exchange. See Flows.
Platform Support Matrix#
| Provider | iOS | Android | Web |
|---|---|---|---|
| native SDK | native SDK | browser flow (popup) | |
| Apple | native sheet | browser flow | browser flow (popup) |
| GitHub | browser flow | browser flow | browser flow (popup) |
| Microsoft | browser flow | browser flow | browser flow (popup) |
macOS, Windows and Linux are not supported: SocialAuth.supports(name) answers false there, and SocialAuthButtons omits the provider.
Registering a Custom Driver#
The backend serves Google, Apple, GitHub and Microsoft. A custom driver is for a provider your own backend adds, or for a different way of running one of these. Extend SocialDriver, or reuse RedirectDriver for a provider the backend hosts a browser flow for:
import 'package:magic_social_auth/magic_social_auth.dart';
SocialAuth.manager.extend(
'gitlab',
(config) => RedirectDriver('gitlab', config),
);
SocialAuth.manager.registerProviderDefaults(
'gitlab',
const SocialProviderDefaults(
label: 'GitLab',
iconSvg: '<svg>...</svg>',
order: 5,
),
);
Call extend() before the first SocialAuth.driver('gitlab'). It clears a cached instance of that name, so re-registering is safe. A custom driver is resolved after the enabled check, so social_auth.providers.gitlab.enabled: false still disables it.
Gotchas#
| Mistake | Fix |
|---|---|
An await before signIn() on the web |
The popup opens in the same synchronous run as the call, so call it straight from the tap handler. For a connect on the web, run beginConnect first and the returned opener from the next tap. |
Caching a proof map or a link ticket across attempts |
Build the proof per call. Every connect mints a new PKCE pair, a new link ticket and a new proof. |
SocialAuth.driver(name) for a provider with enabled: false |
It throws ProviderNotConfiguredException; an unknown name throws ArgumentError. Ask SocialAuth.supports(name) first, which answers false for both. |
A custom provider that never appears in SocialAuthButtons |
The widget walks social_auth.providers, so the provider needs its own entry there as well as extend(). |
Sending a nonce for Google, or the raw nonce to Apple, from a custom driver on SocialFlow.token |
Google takes none. Apple gets Nonce.hashed and the backend gets Nonce.raw. |
Dropping AppleSignInResult.givenName and familyName |
Apple shares the name on the first authorisation only; hand it to a profile update straight away. |
Related Documentation#
- Flows: the browser flow, the native token flow, errors and guarantees.
- Configuration: the keys each driver reads.
- Installation:
social:installandsocial:doctor. - Architecture overview: how the manager, drivers and flow client fit together.