base_idp 0.1.0 copy "base_idp: ^0.1.0" to clipboard
base_idp: ^0.1.0 copied to clipboard

Official Flutter and Dart SDK for Base IdP OAuth PKCE login and server handoff.

Base IdP Flutter/Dart SDK #

Official Flutter and Dart SDK for Base IdP authentication.

Base IdP owns identity. Your app owns product data. This SDK connects a Flutter or Dart app to the hosted Base IdP authorization UI, discovers the client configuration from Base IdP, runs OAuth authorization-code flow with PKCE, fetches the authenticated principal, and returns a server handoff payload that your backend can verify and merge with its own user records.

What This SDK Solves #

Most apps should not rebuild login screens, password handling, magic links, or identity verification. Base IdP handles that centrally.

Your Flutter app:

  1. Provides its Base IdP client ID.
  2. Opens the hosted Base IdP auth interface.
  3. Receives a deep-link callback.
  4. Exchanges the code with PKCE.
  5. Gets a Base identity payload.
  6. Sends that payload to your backend.

Your backend:

  1. Verifies the Base token.
  2. Finds or creates the product user by stable Base ID and/or email.
  3. Returns product-specific data, roles, projects, billing, teams, and settings.

That keeps identity portable across product surfaces while leaving every product in control of its own database.

Install #

From pub.dev:

flutter pub add base_idp

Or add it manually:

dependencies:
  base_idp: ^0.1.0

For local SDK development before publishing:

dependencies:
  base_idp:
    path: /Users/ajmaljs/Developer/Square/products/base-idp/sdk/dart

Then run:

flutter pub get

Keys And Environments #

For a Flutter mobile app, use only:

BASE_IDP_CLIENT_ID

Do not put BASE_IDP_CLIENT_SECRET in Flutter, iOS, Android, web, or desktop client binaries. Mobile apps are public OAuth clients. They must use PKCE.

Server-side Dart can use:

BASE_IDP_CLIENT_ID
BASE_IDP_CLIENT_SECRET

The SDK also accepts the legacy BASE_IDP_SECRET value as a fallback for server-side Dart, but new apps should use BASE_IDP_CLIENT_SECRET.

Do not duplicate these in app env files:

  • Scopes
  • Audience
  • Redirect URI lists
  • Allowed origins
  • Auth methods
  • Requested claims

Those belong in the Base IdP client registration. The SDK discovers them from:

POST /v1/client-config

Issuer #

The SDK uses the production Base IdP issuer:

https://authlayer.squareexp.com

Run the app with the client ID:

flutter run --release \
  --dart-define=BASE_IDP_CLIENT_ID=sq_live_mobile_xxxxx

Register A Mobile Client In Base IdP #

Create a public mobile client in Base IdP. The client must not be confidential. If the SDK discovers that the client is confidential, mobile login fails with:

confidential_client_in_mobile

Register the exact redirect URI your app will receive:

com.example.app://auth/callback

The redirect URI must match exactly. These are different redirect URIs:

com.example.app://auth/callback
com.example.app:/auth/callback
com.example.app://callback

Flutter Login #

import 'package:base_idp/base_idp.dart';

final auth = BaseIdpFlutterAuth.fromEnvironment(
  redirectUri: 'com.example.app://auth/callback',
);

Future<void> signIn() async {
  try {
    final session = await auth.login();

    print(session.principal.id);
    print(session.principal.email);
    print(session.principal.name);

    final payload = session.toServerPayload().toJson();
    await sendIdentityToBackend(payload);
  } on BaseIdpException catch (error) {
    // Show a clear product-specific UI message.
    print('${error.code}: ${error.message}');
  }
}

Future<void> sendIdentityToBackend(Map<String, Object?> payload) async {
  // POST this payload to your backend login/session endpoint.
}

The BaseIdpMobileSession contains:

session.tokens.accessToken;
session.tokens.refreshToken;
session.tokens.tokenType;
session.tokens.expiresIn;
session.principal.id;
session.principal.subject;
session.principal.email;
session.principal.name;
session.principal.role;
session.principal.accountContext;
session.callback.code;
session.callback.state;

Use session.toServerPayload() when sending identity proof to a backend.

The SDK uses app_links to receive the OAuth callback.

Android #

Add an intent filter for your redirect scheme in android/app/src/main/AndroidManifest.xml:

<activity
    android:name=".MainActivity"
    android:exported="true"
    android:launchMode="singleTop"
    android:theme="@style/LaunchTheme"
    android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
    android:hardwareAccelerated="true"
    android:windowSoftInputMode="adjustResize">

    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>

    <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="com.example.app"
            android:host="auth"
            android:path="/callback" />
    </intent-filter>
</activity>

This matches:

com.example.app://auth/callback

iOS #

Add a URL type in ios/Runner/Info.plist:

<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLName</key>
    <string>com.example.app</string>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>com.example.app</string>
    </array>
  </dict>
</array>

Then use a redirect URI that starts with that scheme:

com.example.app://auth/callback

Universal links and Android app links are also valid, but they must be registered exactly in Base IdP.

Manual Callback Handling #

If your app already has a router that receives app links, you can parse and complete the callback yourself.

final auth = BaseIdpFlutterAuth.fromEnvironment(
  redirectUri: 'com.example.app://auth/callback',
);

final callback = auth.parseCallback(incomingUri);

final session = await auth.completeCallback(
  incomingUri,
  codeVerifier: storedCodeVerifier,
);

Use this only if your app owns the PKCE transaction state itself. The normal login() method handles state, PKCE, browser launch, callback waiting, token exchange, and /v1/me for you.

Direct Dart Client #

Use BaseIdpClient when you need lower-level control or server-side Dart.

import 'package:base_idp/base_idp.dart';

final client = BaseIdpClient.fromEnvironment(
  redirectUri: 'com.example.app://auth/callback',
);

final config = await client.resolveConfig();

print(config.clientId);
print(config.displayName);
print(config.allowedRedirectUris);
print(config.allowedScopes);
print(config.allowedAuthMethods);

Build an authorize URL manually:

final pkce = BaseIdpPkcePair.generate();
final state = randomBaseIdpState();

final authorizeUri = await client.authorizeUri(
  redirectUri: 'com.example.app://auth/callback',
  state: state,
  pkce: pkce,
);

Exchange a code:

final tokens = await client.exchangeCode(
  code: code,
  redirectUri: 'com.example.app://auth/callback',
  codeVerifier: pkce.verifier,
);

final principal = await client.me(tokens.accessToken);

Refresh tokens:

final refreshed = await client.refresh(
  refreshToken: tokens.refreshToken,
);

Server Handoff Pattern #

Mobile should not be the final authorization boundary. Use mobile login to get identity proof, then let your backend create the product session.

Flutter:

final session = await auth.login();

await http.post(
  Uri.parse('https://api.example.com/auth/base-idp/session'),
  headers: {'Content-Type': 'application/json'},
  body: jsonEncode(session.toServerPayload().toJson()),
);

Backend responsibilities:

  1. Verify the PASETO access token with the Base IdP public key.
  2. Read the principal (gid, sub, email, name, ctx, role, amr).
  3. Find or create the product user by Base gid.
  4. If older data exists by email, link it to the Base gid.
  5. Return the product session and product-specific user schema.

Do not rely on the Flutter app to assign product permissions. The mobile app can submit identity proof; the backend decides product authorization.

Error Handling #

All SDK errors use BaseIdpException.

try {
  final session = await auth.login();
} on BaseIdpException catch (error) {
  switch (error.code) {
    case 'invalid_config':
      // BASE_IDP_CLIENT_ID is missing.
      break;
    case 'config_discovery_failed':
      // Client id is wrong, inactive, or the issuer is wrong.
      break;
    case 'confidential_client_in_mobile':
      // Use a public mobile client, not a confidential backend client.
      break;
    case 'browser_launch_failed':
      // The system browser could not be opened.
      break;
    case 'callback_timeout':
      // Browser opened, but no matching app callback arrived.
      break;
    case 'oauth_callback_error':
      // Base IdP returned error/error_description in callback.
      break;
    case 'token_exchange_failed':
      // Code, redirect URI, client auth, or PKCE verification failed.
      break;
    case 'principal_fetch_failed':
      // /v1/me rejected the access token.
      break;
    default:
      // Product fallback error UI.
      break;
  }
}

Never log:

  • Access tokens
  • Refresh tokens
  • Client secrets
  • Authorization codes
  • PKCE verifiers
  • Full callback URLs

Integration Checklist #

Run the Flutter app with a production Base IdP client:

flutter run \
  --dart-define=BASE_IDP_CLIENT_ID=sq_live_mobile_xxxxx

Verify:

  • The client ID exists in Base IdP.
  • The redirect URI is registered exactly.
  • The client is public for mobile usage.
  • The hosted auth UI opens.
  • Login returns to the app.
  • The code exchange succeeds.
  • /v1/me returns the expected Base identity.
  • The backend can merge the identity into the product user model.

Package Quality Checks #

Run these before committing SDK changes:

dart format .
flutter analyze
flutter test
dart pub publish --dry-run

Expected package structure:

lib/base_idp.dart
lib/src/client.dart
lib/src/config.dart
lib/src/errors.dart
lib/src/mobile_auth.dart
lib/src/pkce.dart
lib/src/types.dart
test/base_idp_test.dart

Design Rules #

  • Use OAuth authorization-code flow with PKCE.
  • Do not add implicit flow.
  • Do not embed client secrets in Flutter apps.
  • Do not hardcode scopes or audience in the mobile app unless you are doing a temporary compatibility test.
  • Let Base IdP own identity policy.
  • Let the product backend own product data and authorization.
  • Prefer /v1/me and backend verification over trusting locally decoded token content.
  • Keep token storage minimal. If refresh tokens must persist, use platform secure storage and document the risk.
  • Merge users by stable Base gid, with email as a migration/linking fallback.

Common Problems #

BASE_IDP_CLIENT_ID is required #

Pass the client ID at build/run time:

flutter run --dart-define=BASE_IDP_CLIENT_ID=sq_live_mobile_xxxxx

invalid_redirect_uri #

The callback URI used by the app is not registered exactly in Base IdP. Register the exact value, including scheme, host, path, and port when applicable.

invalid_client #

The client ID is wrong, inactive, pointed at the wrong issuer, or belongs to another environment.

confidential_client_in_mobile #

You are trying to use a confidential client in a mobile app. Create a public mobile client in Base IdP.

token_exchange_failed #

Check that:

  • The authorization code was not reused.
  • The redirectUri in the token exchange exactly matches authorization.
  • The PKCE verifier matches the original challenge.
  • The app is using the same issuer during start and callback.

License #

See LICENSE.

1
likes
110
points
78
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Official Flutter and Dart SDK for Base IdP OAuth PKCE login and server handoff.

Topics

#oauth2 #authentication #pkce #sso

License

unknown (license)

Dependencies

app_links, crypto, flutter, http, meta, url_launcher

More

Packages that depend on base_idp