Installation
Social sign-in for Flutter apps built on the Magic Framework, against the magic-starter-laravel backend: native Google and Apple sheets on mobile, a backend-hosted browser flow (PKCE) for GitHub, Microsoft and everything else, on iOS, Android and web.
On this page
Requirements#
| Dependency | Version |
|---|---|
| Dart SDK | ^3.12.0 |
| Flutter | >=3.44.0 |
| magic | ^0.0.29 |
| fluttersdk_artisan | ^0.0.20 |
| Backend | magic-starter-laravel with the social-login feature on |
The Dart and Flutter floors come from sign_in_with_apple 8.2. The package depends on google_sign_in ^7.2.0, sign_in_with_apple ^8.2.0, flutter_web_auth_2 ^5.1.0 and crypto ^3.0.7.
The backend side (routes, provider keys, the error codes) is documented in magic-starter-laravel's doc/basics/social-login.md and doc/basics/account-deletion.md; this package is its client and the two must agree on every value listed under Backend Environment.
Install the Package#
Add magic_social_auth to your pubspec.yaml:
dependencies:
magic_social_auth: ^0.0.11
Fetch dependencies, then register the package's artisan commands (social:install, social:doctor):
flutter pub get
dart run fluttersdk_artisan plugin:install magic_social_auth
Run social:install#
One non-interactive run leaves the app ready to sign in. Replace the values with your own:
dart run <app>:artisan social:install \
--providers=google,apple,github,microsoft \
--google-ios-client-id=123-abc.apps.googleusercontent.com \
--google-server-client-id=123-def.apps.googleusercontent.com \
--ios-scheme=myapp \
--android-callback=https://auth.example.com/auth/social
Preview without writing anything:
dart run <app>:artisan social:install --dry-run
<app> is your app's pubspec package name. Every flag is optional; an omitted value is written as an empty string in the config and social:doctor names it until you fill it.
Flags#
| Flag | Default | What it does |
|---|---|---|
--providers= |
google,apple,github,microsoft |
Comma-separated providers to enable. Each becomes enabled: true in the config, the rest enabled: false. An unknown name stops the run. Google and Apple only add their native iOS edits when they are in the list. |
--google-ios-client-id= |
empty | The Google iOS OAuth client id (123-abc.apps.googleusercontent.com). Written to the config as google.ios_client_id, to Info.plist as GIDClientID, and its reversed form (com.googleusercontent.apps.123-abc) is registered as a URL scheme. Needs google in --providers. |
--google-server-client-id= |
empty | The Google web OAuth client id, the audience the backend verifies. Written to the config as google.server_client_id and to Info.plist as GIDServerClientID. Needs google in --providers. |
--ios-scheme= |
empty | The custom URL scheme the browser flow returns to on iOS (myapp). Written to the config as callback.ios = myapp://auth/social. A value that is not a URL scheme stops the run. |
--android-callback= |
empty | The https App Link the browser flow returns to on Android (https://auth.example.com/auth/social). Written to the config as callback.android and declared as the CallbackActivity intent filter (host and path). Must be an https URL with a host and a path, without a query or fragment; use a host your app's other App Link filters do not claim (see Android). |
--force |
off | Overwrite files that were changed since the last install, or that the installer did not write. Without it such a file is reported as a conflict and the run exits 1. |
--dry-run |
off | Print every staged operation and write nothing. |
--non-interactive |
off | Inherited from every artisan install command. social:install asks no questions, so it changes nothing; it is safe to pass in CI. |
--no-bootstrap |
off | Inherited from every artisan install command. social:install does not read it, and the post-install message is printed either way. |
The Google client ids must look like 123-abc.apps.googleusercontent.com; anything else stops the run before a file is touched.
Re-running with different flag values rewrites the files the installer created while you have not edited them. A file you edited is reported as a conflict, and --force overwrites it.
What social:install Writes#
| Target | Written when | Content |
|---|---|---|
lib/config/app.dart |
always | SocialAuthServiceProvider is added to the providers list. |
lib/config/social_auth.dart |
always | The config (see Configuration), and () => socialAuthConfig is added to Magic.init's configFactories in lib/main.dart. |
ios/Runner/Info.plist |
google enabled and --google-ios-client-id given |
GIDClientID and the reversed client id as a CFBundleURLTypes scheme. |
ios/Runner/Info.plist |
google enabled and --google-server-client-id given |
GIDServerClientID. |
| Every entitlements file the iOS app target signs with | apple enabled |
com.apple.developer.applesignin = [Default], merged into any array the file already has. A project that signs with no entitlements file gets ios/Runner/Runner.entitlements and the target is pointed at it. |
android/app/src/main/AndroidManifest.xml |
--android-callback given |
The com.linusu.flutter_web_auth_2.CallbackActivity activity: exported="true", taskAffinity="", one autoVerify intent filter (VIEW, DEFAULT, BROWSABLE, scheme https, the callback's host and path). An existing activity with different filters is never rewritten; the install warns with the block it expected. |
web/auth.html |
the project has a web/ directory |
The page the web popup posts its result back through. |
lib/app/providers/social_auth_starter_service_provider.dart |
magic_starter is in pubspec.yaml |
The bridge that plugs this package into the starter's screens, registered in lib/config/app.dart. |
The browser flow's iOS custom scheme is not registered in Info.plist: ASWebAuthenticationSession delivers the return only to the app that started it. The Android Google sign-in needs no manifest edit.
Run social:doctor#
dart run <app>:artisan social:doctor
Every way of getting the native half wrong is silent: a sign-in sheet that closes at once, a button that does nothing, a review rejection months later. The doctor reads lib/config/social_auth.dart, Info.plist, the entitlements files the app target signs with, AndroidManifest.xml, web/ and the pubspec, and names what is missing. A section is printed only when the project has that platform. It exits 0 when every check passes and 1 otherwise.
| Section | Checks |
|---|---|
| Config | The config exists; at least one provider is enabled; google.ios_client_id and google.server_client_id are set when Google is on; callback.ios is set when a browser-flow provider needs it on iOS; callback.android is set and is an https URL with a host and a path when a browser-flow provider needs it on Android. |
| iOS | Info.plist has GIDClientID, GIDServerClientID and the reversed client id URL scheme; Sign in with Apple is in every entitlements file the app target signs with; Apple is not disabled while another provider is enabled (App Store guideline 4.8 asks an iOS app that offers another third-party sign-in to offer Sign in with Apple too). |
| Android | The CallbackActivity is declared with exported="true", taskAffinity="" and an autoVerify https filter for the callback; no other activity's App Link filter overlaps the callback. |
| Web | web/auth.html exists and posts the flutter-web-auth-2 message back to the app. |
| magic_starter bridge | Only when magic_starter is a dependency: the bridge file exists and SocialAuthStarterServiceProvider is registered in lib/config/app.dart. |
The doctor cannot prove that an Android App Link verifies (Google fetches assetlinks.json at install time) or that a provider console lists the callback; those stay a device and a console check.
Backend Environment#
After the checks, the doctor prints the .env lines of magic-starter-laravel that mirror your config, and the callback each provider console has to list. This is the shape (values come from your config; angle brackets are yours to fill):
Backend env (magic-starter-laravel)
MAGIC_STARTER_SOCIAL_PROVIDERS=google,apple,github,microsoft
MAGIC_STARTER_SOCIAL_IOS_REDIRECT=myapp://auth/social
MAGIC_STARTER_SOCIAL_ANDROID_REDIRECT=https://auth.example.com/auth/social
MAGIC_STARTER_SOCIAL_WEB_REDIRECT=https://<app origin>/auth.html
MAGIC_STARTER_SOCIAL_GOOGLE_AUDIENCES=123-abc.apps.googleusercontent.com,123-def.apps.googleusercontent.com
MAGIC_STARTER_SOCIAL_APPLE_AUDIENCES=<ios bundle id>
MAGIC_STARTER_APPLE_TEAM_ID=
MAGIC_STARTER_APPLE_KEY_ID=
MAGIC_STARTER_APPLE_PRIVATE_KEY=
MAGIC_STARTER_APPLE_BUNDLE_ID=<ios bundle id>
MAGIC_STARTER_APPLE_SERVICES_ID=
https://<api host>/magic-starter/social/google/callback
https://<api host>/magic-starter/social/apple/callback
https://<api host>/magic-starter/social/github/callback
https://<api host>/magic-starter/social/microsoft/callback
The Apple lines appear only when Apple is enabled, the Google audiences line only when Google is, and one callback URL is printed per enabled provider.
MAGIC_STARTER_SOCIAL_IOS_REDIRECTandMAGIC_STARTER_SOCIAL_ANDROID_REDIRECTmust equalsocial_auth.callback.iosandsocial_auth.callback.androidexactly. The backend takes the redirect target only from its own config, so a mismatch means the browser lands somewhere the app is not listening.MAGIC_STARTER_SOCIAL_WEB_REDIRECTis the app's ownauth.html, on the origin the web build is served from.- The Google audiences are the client ids whose ID tokens the backend accepts: the iOS client id and the web (server) client id.
- The browser flow starts at
<network.drivers.api.base_url>/auth/social/{provider}/redirect, so the app'snetwork.drivers.api.base_urlhas to be the backend's API base.
Provider Consoles#
Register the fixed callback URL in every provider console. It does not use the backend's route prefix:
https://<api host>/magic-starter/social/{provider}/callback
| Provider | In the console |
|---|---|
A web OAuth client with the callback as an authorized redirect URI; its client id is --google-server-client-id. An iOS OAuth client for the app's bundle id; its client id is --google-ios-client-id. An Android OAuth client for the app's package name and signing SHA-1. |
|
| Apple | A Services ID with the callback as a return URL (the browser flow runs as this client, for Android and web). A Sign in with Apple key (.p8) for the Team ID and Key ID. The iOS app's capability for Sign in with Apple (the entitlement social:install writes). Optionally, https://<api host>/magic-starter/social/apple/notifications as the server-to-server notification endpoint. |
| GitHub | An OAuth app whose authorization callback URL is the callback. |
| Microsoft | An Entra app registration with the Web platform and the callback as a redirect URI. The backend uses the common tenant unless MICROSOFT_TENANT says otherwise. |
Client secrets and Apple's key live in the backend's environment, never in the Flutter app. See magic-starter-laravel's doc/basics/social-login.md for the config/services.php keys.
Platform Notes#
iOS#
- Google and Apple sign in through their native sheets. GitHub and Microsoft open
ASWebAuthenticationSessionand return through the custom scheme from--ios-scheme. - The callback is a custom scheme on purpose: an https callback needs iOS 17.4.
ASWebAuthenticationSessiondelivers the return only to the app that started it, so a custom scheme is safe. - Confirming an identity (the step-up) opens an ephemeral session, so the provider asks for the account again instead of waving a remembered one through.
- Sign in with Apple hands out the user's name on the first authorization only.
AppleSignInResult.givenNameandfamilyNamecarry it; pass them on to a profile update or they are lost.
Android#
- Google signs in through the native Credential Manager; Apple, GitHub and Microsoft use the browser flow.
- The callback is an https App Link on a dedicated host (
auth.example.com), not the app's main domain. The app's other App Link filters must not claim that host: when one does, Android may open that activity instead of the callback activity, andsocial:doctorflags the overlap. - The host must serve a Digital Asset Links file (
/.well-known/assetlinks.json) for the app, since the filter isautoVerify. - An Auth Tab whose App Link verification failed closes at once and reports a plain cancel (flutter_web_auth_2 issue 215), so a cancel is not always the user's. The cancel copy invites a retry.
- A misconfigured Google client (SHA-1, package name,
server_client_id) is also reported as a cancel by the SDK. - The package never sets
preferEphemeralon Android.
Web#
- All four providers use the browser flow in a popup.
web/auth.htmlreceives the callback inside the popup and posts it back to the app. - The popup must open from the tap itself: call
SocialAuth.driver(name).signIn()straight from the tap handler, with noawaitbefore it.SocialAuthButtonscallsonPressedsynchronously for this reason. - Do not serve the app shell or
auth.htmlwithCross-Origin-Opener-Policy: same-origin. It severs the popup's link to the app (window.opener), which is how the result gets back. - A new click supersedes a popup that is still pending; only the newest flow spends its code.
macOS, Windows, Linux#
Not supported. SocialAuth.supports(name) answers false and SocialAuthButtons renders nothing for the provider.
Without magic_starter#
The package works on its own: the drivers answer a SocialAuthResult and the app decides what a session, a 2FA challenge or a cancelled deletion means (see Flows). Without magic_starter there is no bridge, so the app owns one more thing: sign Google out on its own sign-out. The Google SDK keeps its own session; if it is not signed out, the next sign-in skips the account picker. The bridge does this for starter apps on every transition to signed-out. In an app without it:
Auth.stateNotifier.addListener(() {
if (!Auth.check()) {
unawaited(SocialAuth.driver('google').signOut());
}
});
Call the driver by name rather than SocialAuth.signOut(): the manager only signs out drivers it has cached in this process, and a session restored at boot has created none. Guard the call to google being enabled and the platform being iOS or Android.
Manual Setup#
social:install is the supported path. To wire it by hand: register the provider and the config factory,
// lib/config/app.dart
(app) => SocialAuthServiceProvider(app),
// lib/main.dart
await Magic.init(
configFactories: [
() => appConfig,
() => socialAuthConfig,
],
);
and make the native edits listed under What social:install Writes. SocialAuthServiceProvider must come after your auth provider, because SocialAuthManager.createUser delegates to Auth.manager.createUser.
Verify the Installation#
import 'package:magic_social_auth/magic_social_auth.dart';
if (SocialAuth.supports('google')) {
// Google is available on this platform.
}
Then run social:doctor and sign in once per provider on a device.
Related