---
name: authcentral-sdk
description: Use when integrating the AuthCentral Flutter SDK into a host app — installation, initialization, opening the auth screen (full-page or bottom sheet), wiring providers (email, Google, Apple, X, Telegram, wallets), reading/refreshing tokens, theming (built-in or fully custom, including shape tokens), and localization. Covers required platform setup on iOS/Android and backend prerequisites for specific providers.
---

# AuthCentral Flutter SDK — integration skill

A compact, LLM-oriented reference for integrating the AuthCentral SDK
(`authcentral_sdk`) into a host Flutter application. Every code block is
copy-pasteable. Prefer the snippets here over inventing APIs — every public
symbol shown below is part of the current SDK surface.

## 1. What the SDK does

- Provides a **drop-in authentication screen** (`SsoSdk.authScreen()`) with full
  internal routing: login, signup, email/phone verification, backup, success.
- Exposes a **reactive token store** (`SsoSdk.instance.tokenStore`) and
  **auth controller** (`SsoSdk.instance.authController`) for headless use.
- Supports email/password, Google, Apple, X (Twitter), Telegram and
  ETH/SOL/TON wallets.
- Ships three built-in themes (Chatoshi, VIP, Hero) in dark + light variants;
  fully custom themes are supported.
- 17 languages with auto-detection from device locale and per-string overrides.

**Distribution:** GitLab git dependency. **Platforms:** iOS 14+, Android API 23+.
**Requires:** Flutter ≥ 3.41.0, Dart ≥ 3.11.0.

## 2. Golden-path quick start

Minimal working integration — copy, replace the three placeholders, ship.

```yaml
# pubspec.yaml
dependencies:
  authcentral_sdk:
    git:
      url: <gitlab-repo-url>
      path: authCentralSDK
```

```dart
// lib/main.dart
import 'package:authcentral_sdk/authcentral_sdk.dart';
import 'package:flutter/material.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await SsoSdk.initialize(SsoConfig(
    baseUrl: 'https://api.authcentral-1.hero-dev.xyz',
    origin: SsoOrigin.heroDev,
    appName: 'MyApp',
    theme: SsoResolvedTheme.heroDark(),
  ));

  runApp(const MyApp());
}

// Somewhere in your UI:
Future<void> openAuth(BuildContext context) async {
  const appToken = String.fromEnvironment('APP_TOKEN');
  final authToken = await SsoSdk.instance.getAccessToken(appToken);

  await Navigator.of(context).push(
    MaterialPageRoute(
      builder: (_) => SsoSdk.authScreen(
        enabledProviders: const [
          SsoProvider.emailPassword,
          SsoProvider.google,
          SsoProvider.apple,
        ],
        authToken: authToken,
        onSuccess: (tokens) => Navigator.of(context).pop(),
        onCancel: () => Navigator.of(context).pop(),
      ),
    ),
  );
}

// Reactive read:
StreamBuilder<SsoTokens?>(
  stream: SsoSdk.instance.tokenStore.tokensStream,
  initialData: SsoSdk.instance.tokenStore.currentTokens,
  builder: (ctx, snap) => snap.data != null ? HomePage() : LoginPage(),
);
```

**Two-token model.** `appToken` (app-private) is passed to
`getAccessToken()` to mint an `authToken`. The `authToken` is passed to
`authScreen()`. After successful auth, `SsoTokens` (authToken + refreshToken)
are stored in `SsoSdk.instance.tokenStore` and streamed to you.

## 3. Installation

### 3.1 Dependency

```yaml
dependencies:
  authcentral_sdk:
    git:
      url: <gitlab-repo-url>
      path: authCentralSDK
```

Run: `flutter pub get`.

### 3.2 iOS (`ios/Runner/Info.plist`)

Google Sign-In — add the reversed client ID:

```xml
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>com.googleusercontent.apps.YOUR_CLIENT_ID</string>
    </array>
  </dict>
</array>
```

Deep links (wallets, X) — add your app scheme to the same `CFBundleURLTypes`
array (alongside Google, not instead):

```xml
<dict>
  <key>CFBundleURLSchemes</key>
  <array><string>heroapp</string></array>
</dict>
```

### 3.3 Android (`android/app/src/main/AndroidManifest.xml`)

```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="heroapp" />
</intent-filter>
```

For Google Sign-In on Android also drop `google-services.json` into
`android/app/`.

### 3.4 App secrets

Keep `APP_TOKEN` out of source. Use `--dart-define-from-file=.env` and
`String.fromEnvironment('APP_TOKEN')`.

## 4. Initialization — `SsoConfig`

`SsoSdk.initialize()` must be called **once** before any other SDK call.

```dart
await SsoSdk.initialize(SsoConfig(
  // Required
  baseUrl: 'https://api.authcentral-1.hero-dev.xyz',
  origin: SsoOrigin.heroDev,
  appName: 'MyApp',

  // Branding
  logo: 'assets/icons/my_logo.svg', // asset path or https:// URL

  // Theme (default: SsoResolvedTheme.chatoshiDark())
  theme: SsoResolvedTheme.heroDark(),

  // Localization (default: auto-detect from device)
  locale: const Locale('en'),
  textOverrides: {'login_title': 'Welcome back!'},

  // Deep links for wallets / X / Telegram
  appScheme: 'heroapp',

  // Wallet provider configuration (required if SsoProvider.wallet is used).
  // EVM wraps reown_appkit — see §6.1 below for the full reown reference
  // pointers (installation, usage, Link Mode, dashboard).
  walletConfig: const SsoWalletConfig(
    evm: SsoEvmWalletConfig(
      projectId: 'your-project-id',
      // universalLink: 'https://deeplinks.example.com/wc',
    ),
    sol: SsoSolWalletConfig(),
    ton: SsoTonWalletConfig(
      manifestUrl: 'https://example.com/tonconnect-manifest.json',
    ),
  ),

  // Analytics
  analytics: SsoAnalyticsConfig(
    onEvent: (event) => FirebaseAnalytics.instance
        .logEvent(name: event.name, parameters: event.properties),
    onError: (error) => Sentry.captureException(error),
    suppressedEvents: {'sso_screen_viewed'},
  ),

  // Global callbacks (fire on every successful auth, across screens)
  onAuthSuccess: (tokens) => print('Authenticated'),
  onAuthFailure: () => print('Auth failed'),

  // Environment / networking / security
  environment: SsoEnvironment.production,
  debug: false,
  connectTimeout: const Duration(seconds: 30),
  receiveTimeout: const Duration(seconds: 30),
  allowInsecureDevices: false,
));
```

### 4.1 Parameter reference

| Parameter | Type | Required | Default | Notes |
|-----------|------|----------|---------|-------|
| `baseUrl` | `String` | yes | — | AuthCentral backend URL |
| `origin` | `SsoOrigin` | yes | — | Sent as `Origin` header (see below) |
| `appName` | `String` | yes | — | Shown in auth screens |
| `logo` | `String?` | no | `null` | Asset path or URL |
| `theme` | `SsoResolvedTheme?` | no | `chatoshiDark` | See §8 |
| `locale` | `Locale?` | no | `null` → auto | See §9 |
| `textOverrides` | `Map<String,String>?` | no | `null` | Per-key string overrides |
| `appScheme` | `String?` | no | `null` | Deep-link scheme (wallets, X) |
| `walletConfig` | `SsoWalletConfig?` | no | `null` | Required for wallets |
| `analytics` | `SsoAnalyticsConfig?` | no | `null` | Event + error forwarding |
| `onAuthSuccess` | `Function(SsoTokens)?` | no | `null` | Global success hook |
| `onAuthFailure` | `Function()?` | no | `null` | Global failure hook |
| `environment` | `SsoEnvironment` | no | `production` | |
| `debug` | `bool` | no | `false` | Verbose logs |
| `connectTimeout` | `Duration` | no | `30s` | |
| `receiveTimeout` | `Duration` | no | `30s` | |
| `allowInsecureDevices` | `bool` | no | `false` | Rooted/jailbroken |
| `tokenStorageAdapter` | `TokenStorageAdapter?` | no | `null` | Override secure storage |
| `httpInterceptors` | `List<dynamic>?` | no | `null` | Extra Dio interceptors |

### 4.2 `SsoOrigin` values

| Enum | URL | Use for |
|------|-----|---------|
| `SsoOrigin.heroProd` | `https://auth.hero.io` | Hero production |
| `SsoOrigin.heroDev` | `https://authcentral-1.hero-dev.xyz` | Hero dev |
| `SsoOrigin.chatoshiProd` | `https://auth.chatoshi.ai` | Chatoshi production |
| `SsoOrigin.chatoshiDev` | `https://auth.chatoshi.dev` | Chatoshi dev |
| `SsoOrigin.vipProd` | `https://auth.vip.ai` | VIP production |
| `SsoOrigin.vipDev` | `https://authcentral-1.dev-vip.net` | VIP dev |

### 4.3 Teardown

```dart
SsoSdk.dispose(); // releases all SDK resources; re-init to use again
```

## 5. Opening the auth screen

Two presentation modes — same parameters (plus sheet-specific ones).

### 5.1 Full-page (`Navigator.push`)

```dart
Navigator.of(context).push(MaterialPageRoute(
  builder: (_) => SsoSdk.authScreen(
    enabledProviders: const [
      SsoProvider.emailPassword,
      SsoProvider.google,
      SsoProvider.apple,
      SsoProvider.telegram,
      SsoProvider.x,
      SsoProvider.wallet,
    ],
    enabledWallets: const [
      SsoWalletType.metaMask,
      SsoWalletType.trustWallet,
      SsoWalletType.walletConnect,
      SsoWalletType.phantom,
      SsoWalletType.tonKeeper,
    ],
    authToken: authToken,
    theme: SsoResolvedTheme.heroDark(),     // overrides init-level theme
    locale: 'en',                           // overrides init-level locale
    onSuccess: (tokens) => Navigator.of(context).pop(),
    onError: (err) => debugPrint('code=${err.code} msg=${err.message}'),
    onCancel: () => Navigator.of(context).pop(),
    termsUrl: 'https://example.com/terms',
    privacyUrl: 'https://example.com/privacy',
  ),
));
```

### 5.2 Bottom sheet

```dart
await SsoSdk.showAuthBottomSheet(
  context: context,
  enabledProviders: const [SsoProvider.emailPassword, SsoProvider.google],
  authToken: authToken,
  onSuccess: (tokens) {/* sheet auto-dismisses */},
  onCancel: () {/* swipe / scrim tap */},
  // Sheet-specific:
  heightFactor: 0.9,   // fraction of screen height
  topRadius: 20.0,     // logical pixels
  isDismissible: true, // tap scrim dismisses
  enableDrag: true,    // swipe down dismisses
);
```

**When to use each.**
- Push route: app has its own back stack and users expect a dedicated page.
- Bottom sheet: quick sign-in flow, e.g. gating a single action inside an
  existing screen.

### 5.3 Parameters (both modes)

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `enabledProviders` | `List<SsoProvider>` | yes | Auth methods to show |
| `enabledWallets` | `List<SsoWalletType>?` | no | Wallets; requires `SsoProvider.wallet` |
| `authToken` | `String?` | no | Access token from `getAccessToken()` |
| `theme` | `SsoResolvedTheme?` | no | Per-screen theme override |
| `locale` | `String?` | no | Per-screen language (`'en'`, `'ru'`, …) |
| `onSuccess` | `Function(SsoTokens)?` | no | Called on success |
| `onCancel` | `VoidCallback?` | no | Called on user dismiss |
| `onError` | `Function(SsoError)?` | no | Called on critical errors |
| `termsUrl` | `String?` | no | Terms of Service URL |
| `privacyUrl` | `String?` | no | Privacy Policy URL |

Internal flow is managed by the SDK:

```
OAuth landing → Login | Sign Up
                     ↓
             Email / Phone verification
                     ↓
             Backup contact
                     ↓
             Success → onSuccess
```

Do **not** wrap `authScreen()` in another `Navigator` — it owns its own.

## 6. Providers

Each provider is opted-in via `enabledProviders`. Some require platform or
backend setup on top of the base install.

| Provider | Enum | Extra setup |
|----------|------|-------------|
| Email / password | `SsoProvider.emailPassword` | None |
| Google | `SsoProvider.google` | iOS reversed-client-id URL scheme; Android `google-services.json` |
| Apple | `SsoProvider.apple` | Backend: `/apple/connect` response must include a `nonce` field |
| X (Twitter) | `SsoProvider.x` | `appScheme`; backend must accept `redirect_uri` on `/x/connect` and whitelist the deep link |
| Telegram | `SsoProvider.telegram` | `appScheme`; verify backend does not validate `origin` |
| Wallet | `SsoProvider.wallet` | `appScheme` + `walletConfig`; see §6.1 |

### 6.1 Wallets

Pass `walletConfig` at init and enable at least one `SsoWalletType`:

```dart
await SsoSdk.initialize(SsoConfig(
  baseUrl: '...', origin: SsoOrigin.heroDev, appName: 'MyApp',
  appScheme: 'myapp',
  walletConfig: const SsoWalletConfig(
    evm: SsoEvmWalletConfig(
      projectId: 'your-project-id',
      // universalLink: 'https://deeplinks.example.com/wc',
    ),
    sol: SsoSolWalletConfig(),
    ton: SsoTonWalletConfig(
      manifestUrl: 'https://example.com/tonconnect-manifest.json',
    ),
  ),
));

SsoSdk.authScreen(
  enabledProviders: const [SsoProvider.wallet],
  enabledWallets: const [
    SsoWalletType.metaMask,
    SsoWalletType.trustWallet,
    SsoWalletType.walletConnect,
    SsoWalletType.phantom,
    SsoWalletType.tonKeeper,
  ],
  authToken: authToken,
);
```

**Reown references (EVM only):**

- [Reown AppKit Flutter — Installation](https://docs.reown.com/appkit/flutter/core/installation) — base setup, Android/iOS manifest requirements, deep-link plumbing.
- [Reown AppKit Flutter — Usage](https://docs.reown.com/appkit/flutter/core/usage) — `PairingMetadata` / `Redirect`, chain configuration, session options. `SsoEvmWalletConfig` mirrors the useful subset.
- [Reown AppKit Flutter — Link Mode](https://docs.reown.com/appkit/flutter/core/link-mode) — opt in via `SsoEvmWalletConfig.universalLink`; requires AASA + assetlinks hosting and forwarding URIs through `SsoSdk.dispatchWalletConnectLink`.
- [Reown Dashboard](https://dashboard.reown.com) — obtain `SsoEvmWalletConfig.projectId`.
- [`reown_appkit` on pub.dev](https://pub.dev/packages/reown_appkit) — upstream package reference.

| Wallet | Enum | Chain | Protocol |
|--------|------|-------|----------|
| MetaMask | `SsoWalletType.metaMask` | ETH | WalletConnect v2 / deep link |
| Trust Wallet | `SsoWalletType.trustWallet` | ETH | WalletConnect v2 / deep link |
| WalletConnect | `SsoWalletType.walletConnect` | ETH (universal) | WalletConnect v2 |
| Phantom | `SsoWalletType.phantom` | SOL | Deep link + X25519 |
| Tonkeeper | `SsoWalletType.tonKeeper` | TON | TON Connect v2 (SSE bridge) |

## 7. Post-auth API — tokens, state, helpers

Everything useful lives under `SsoSdk.instance`.

### 7.1 Token store (`SsoSdk.instance.tokenStore`)

```dart
final store = SsoSdk.instance.tokenStore;

store.currentTokens;     // SsoTokens? (snapshot)
store.authToken;         // String?
store.refreshToken;      // String?
store.isAuthenticated;   // bool

store.tokensStream.listen((tokens) {
  if (tokens == null) {/* logged out */}
});
```

Tokens are persisted in `flutter_secure_storage` — never in SharedPreferences,
SQLite or files. Never log them.

### 7.2 Auth controller (`SsoSdk.instance.authController`)

```dart
final c = SsoSdk.instance.authController;

c.stateStream.listen((state) {
  switch (state) {
    case SsoAuthenticated():    // signed in
    case SsoUnauthenticated():  // signed out
    case SsoAuthenticating():   // in progress
    case SsoTokenRefreshing():  // silent refresh
    case SsoAuthError(:final error): // failed
  }
});

// Manual refresh (usually unnecessary — SDK refreshes at 80% TTL)
final res = await c.refreshToken();
switch (res) {
  case SsoSuccess(): // ok
  case SsoFailure(:final error): // handle
}

await c.logout(); // clears tokens + session state
```

### 7.3 Helpers

```dart
// Mint an access token for the auth flow
final authToken = await SsoSdk.instance.getAccessToken('app-private-token');

// Metadata about the OAuth-initiating side app
final info = await SsoSdk.instance.getSideApplicationInfo(authToken: authToken);
// info.allowProviders, info.accessToken.payload.redirectUrlOnSuccess

// Supported countries
final publicList  = await SsoSdk.instance.getCountries(authToken: authToken);
final privateList = await SsoSdk.instance.getCountriesPrivate();
```

## 8. Theming

### 8.1 Built-in themes

```dart
SsoResolvedTheme.chatoshiDark();  SsoResolvedTheme.chatoshiLight();
SsoResolvedTheme.vipDark();       SsoResolvedTheme.vipLight();
SsoResolvedTheme.heroDark();      SsoResolvedTheme.heroLight();
```

Each overrides the button/input radius; the rest of the fields are colors:

| Theme | `radiusButton` | `radiusInput` |
|-------|----------------|---------------|
| Chatoshi | `10.0` | `10.0` |
| VIP | `8.0` | `8.0` |
| Hero | `100.0` (pill) | `14.0` |

Set at init (default) or override per screen:

```dart
SsoSdk.initialize(SsoConfig(theme: SsoResolvedTheme.heroDark(), ...));
SsoSdk.authScreen(theme: SsoResolvedTheme.chatoshiLight(), ...);
```

### 8.2 Shape (radius) tokens

| Token | Default | Applied to |
|-------|---------|------------|
| `radiusButton` | `8.0` | Primary/secondary/provider buttons |
| `radiusInput` | `8.0` | Text inputs, dropdowns |
| `radiusBlock` | `24.0` | Cards, info blocks |
| `radiusButtonIcon` | `1000.0` | Round icon buttons (keep large for pill) |
| `radiusBottomSheet` | `8.0` | Auth bottom-sheet top corners |

Defaults above apply when you build a custom `SsoResolvedTheme(...)`; built-in
themes set their own.

### 8.3 Fully custom theme

```dart
final myTheme = SsoResolvedTheme(
  isDark: true,
  fontFamily: 'Roboto',

  // Shape tokens — any subset
  radiusButton: 12.0,
  radiusInput: 12.0,
  radiusBlock: 24.0,
  radiusButtonIcon: 1000.0,
  radiusBottomSheet: 16.0,

  text: SsoTextColors(
    primary: Color(0xFFFFFFFF),
    secondary: Color(0xFFB0B0B0),
    tertiary: Color(0xFF808080),
    disabled: Color(0xFF555555),
    onGradient: Color(0xFFFFFFFF),
    onSnackbarHint: Color(0xFFCCCCCC),
    highlight: Color(0xFFFFD700),
    inverse: SsoInverseTextColors(
      primary: Color(0xFF000000),
      secondary: Color(0xFF333333),
      tertiary: Color(0xFF666666),
    ),
  ),

  brand: SsoBrandColors(
    primary: Color(0xFF6C5CE7),
    onPrimary: Color(0xFFFFFFFF),
    inverse: SsoInverseBrandColors(
      primary: Color(0xFF6C5CE7),
      onPrimary: Color(0xFFFFFFFF),
    ),
  ),

  // ... all other color groups are required — see SsoResolvedTheme constructor
);
```

Color groups (all required in the constructor): `text`, `brand`, `surface`,
`background`, `border`, `interactive`, `alerts`, `accent`, `disabled`,
`elevation`, `tag`, `gradient`, `graphs`, `skeleton`, `native`, `skrim`,
`links`, `logo`, `temporaryChat`, `gradients`, `shadow`.

### 8.4 Extending a built-in theme

Copy every field from the base theme, then override what you need. Easy to
forget the radius fields — if omitted they fall back to constructor defaults
and you lose the base theme's shape:

```dart
final base = SsoResolvedTheme.heroDark();
final myTheme = SsoResolvedTheme(
  text: base.text,           surface: base.surface,
  background: base.background, border: base.border,
  interactive: base.interactive, alerts: base.alerts,
  accent: base.accent,       disabled: base.disabled,
  elevation: base.elevation, tag: base.tag,
  gradient: base.gradient,   graphs: base.graphs,
  skeleton: base.skeleton,   native: base.native,
  skrim: base.skrim,         links: base.links,
  logo: base.logo,           temporaryChat: base.temporaryChat,
  gradients: base.gradients, shadow: base.shadow,
  isDark: base.isDark,       fontFamily: base.fontFamily,

  // Carry over shape tokens — otherwise pills become 8.0 rounded corners
  radiusButton: base.radiusButton,
  radiusInput: base.radiusInput,
  radiusBlock: base.radiusBlock,
  radiusButtonIcon: base.radiusButtonIcon,
  radiusBottomSheet: base.radiusBottomSheet,

  // Only what you're actually changing:
  brand: SsoBrandColors(
    primary: const Color(0xFFFF6B6B),
    onPrimary: const Color(0xFFFFFFFF),
    inverse: const SsoInverseBrandColors(
      primary: Color(0xFFFF6B6B), onPrimary: Color(0xFFFFFFFF),
    ),
  ),
);
```

### 8.5 Reading theme inside custom widgets

```dart
final theme = SsoThemeProvider.of(context);
// theme.brand.primary, theme.text.secondary, theme.radiusButton, ...
```

## 9. Localization

17 languages: `en, ru, de, fr, es, it, pt, pl, tr, ar, zh, ja, ko, hi, id, ms,
fil`. Unsupported locales fall back to `en`.

Priority (highest wins): `authScreen(locale:)` → `SsoConfig(locale:)` →
device locale → English.

```dart
// Global
SsoConfig(locale: const Locale('ru'), ...);

// Per screen
SsoSdk.authScreen(locale: 'fr', ...);
```

Per-string overrides apply across all languages:

```dart
SsoConfig(textOverrides: {
  'login_title': 'Welcome back!',
  'oauth_welcome_title': 'Sign in to {appName}',
});
```

String keys live in `SsoStringKeys`.

## 10. Common recipes

### Gate a screen on auth state

```dart
StreamBuilder<SsoTokens?>(
  stream: SsoSdk.instance.tokenStore.tokensStream,
  initialData: SsoSdk.instance.tokenStore.currentTokens,
  builder: (ctx, snap) => snap.data != null ? const HomePage() : const SignInPage(),
);
```

### Attach the SDK's auth token to your own API calls

```dart
final token = SsoSdk.instance.tokenStore.authToken;
if (token != null) {
  dio.options.headers['Authorization'] = 'Bearer $token';
}
// Or subscribe to tokensStream and refresh the header reactively.
```

### Log user out

```dart
await SsoSdk.instance.authController.logout();
```

### Forward SDK analytics to Firebase

```dart
SsoConfig(
  analytics: SsoAnalyticsConfig(
    onEvent: (e) => FirebaseAnalytics.instance.logEvent(
      name: e.name, parameters: e.properties,
    ),
    onError: (err) => Sentry.captureException(err),
  ),
);
```

## 11. Gotchas

- **Call `initialize()` once before `runApp()`.** Everything else assumes the
  singleton exists.
- **Wallet provider requires `appScheme` + `walletConfig`.** Without the scheme,
  deep-link callbacks silently fail.
- **Apple provider needs backend cooperation.** `/apple/connect` response must
  include a `nonce` field; otherwise auth fails at verification.
- **X provider needs backend cooperation.** `/x/connect` must accept a
  `redirect_uri` parameter and whitelist the deep-link callback URL.
- **Don't store tokens yourself** — the SDK already persists them in
  `flutter_secure_storage`. Reading via `tokenStore` is the canonical path.
- **Never log `authToken` / `refreshToken`.** Use the state stream, not
  `print()`, when debugging.
- **Extending a built-in theme: copy the radius fields.** Omitted radii fall
  back to constructor defaults (8 / 8 / 24 / 1000 / 8), which silently breaks
  pill buttons / custom shape.
- **The auth screen owns its own Navigator.** Don't push nested routes inside
  it; close it via `onSuccess` / `onCancel`.
- **`getAccessToken(appToken)` is required before opening the auth screen** in
  the standard integration. The returned `authToken` is what you pass to
  `authScreen(authToken:)`.
- **Dispose on teardown.** Call `SsoSdk.dispose()` if you ever need to
  re-initialize with different config (e.g. tests, multi-tenant apps).
