diff --git a/README.md b/README.md index 99e6944f..ef8ab4bf 100644 --- a/README.md +++ b/README.md @@ -8,16 +8,43 @@

+
-# Clerk Dart and Flutter SDKs -The official [Clerk](https://clerk.com) Flutter/Dart client library. +[![chat on Discord](https://img.shields.io/discord/856971667393609759.svg?logo=discord)](https://clerk.com/discord) +[![documentation](https://img.shields.io/badge/documentation-clerk-green.svg)](https://clerk.com/docs) +[![twitter](https://img.shields.io/twitter/follow/Clerk?style=social)](https://twitter.com/intent/follow?screen_name=Clerk) +[![melos](https://img.shields.io/badge/maintained%20with-melos-f700ff.svg?style=flat-square)](https://github.com/invertase/melos) +
-* [clerk_auth](./packages/clerk_auth): Dart SDK -* [clerk_flutter](./packages/clerk_flutter): Flutter SDK +# Clerk Flutter and Dart SDKs +**Clerk helps developers build user management. We provide streamlined user experiences for your users to sign up, sign in, and manage their profiles.** -### License +> ### ⚠️ Beta Notice +> These SDKs are currently in Beta. Breaking changes should be expected until the first stable release (1.0.0). Please [file any issues you encounter](https://github.com/clerk/clerk-sdk-flutter/issues). -These SDKs are licensed under the MIT license found in the [LICENSE](./LICENSE) file. +

+ + +
+ The clerk_flutter example app +

+ +--- + +## πŸ“¦ Packages + +| Package | Description | Pub | +|---------|-------------|-----| +| [clerk_auth](./packages/clerk_auth) | Dart SDK for Clerk authentication | [![pub package](https://img.shields.io/badge/pub-v0.0.14--beta-blueviolet)](https://pub.dev/packages/clerk_auth) | +| [clerk_flutter](./packages/clerk_flutter) | Flutter UI components for Clerk authentication | [![pub package](https://img.shields.io/badge/pub-v0.0.14--beta-blueviolet)](https://pub.dev/packages/clerk_flutter) | + + + +## πŸ“„ License + +This project is licensed under the MIT license. + +See [LICENSE](./LICENSE) for more information. diff --git a/assets/example-dark.png b/assets/example-dark.png new file mode 100644 index 00000000..b8f9c1e1 Binary files /dev/null and b/assets/example-dark.png differ diff --git a/assets/example-light.png b/assets/example-light.png new file mode 100644 index 00000000..8de27064 Binary files /dev/null and b/assets/example-light.png differ diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..01db3603 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,240 @@ +# Clerk SDK Documentation + +Welcome to the comprehensive documentation for the Clerk SDK for Flutter and Dart. + +## πŸ“š Documentation Structure + +This documentation is organized into two main sections: + +### 🎯 [clerk_auth](./clerk_auth/) - Core Dart SDK + +The foundational authentication library that works with any Dart application. + +**Start here if you're:** +- Building a Dart CLI application +- Integrating Clerk into a non-Flutter Dart project +- Understanding the core authentication logic +- Implementing custom UI or integrations + +**Key Documentation:** +- [**Auth API**](./clerk_auth/auth.md) - Complete authentication methods (sign-in, sign-up, OAuth, passkeys, sessions, etc.) +- [**AuthConfig**](./clerk_auth/auth_config.md) - SDK configuration and initialization +- [**Persistor**](./clerk_auth/persistor.md) - Custom storage implementations +- [**HttpService**](./clerk_auth/http_service.md) - Custom HTTP client implementations + +### 🎨 [clerk_flutter](./clerk_flutter/) - Flutter Widgets + +Pre-built Flutter widgets and UI components for rapid integration. + +**Start here if you're:** +- Building a Flutter mobile or web application +- Looking for pre-built authentication UI +- Wanting quick integration with minimal code +- Customizing the appearance of auth flows + +**Key Documentation:** +- [**ClerkAuth**](./clerk_flutter/clerk_auth.md) - Root widget and state management +- [**ClerkAuthentication**](./clerk_flutter/clerk_authentication.md) - Complete pre-built auth UI +- [**ClerkUserButton**](./clerk_flutter/clerk_user_button.md) - User profile and session management +- [**ClerkAuthBuilder**](./clerk_flutter/clerk_auth_builder.md) - Custom UI with builder pattern +- [**ClerkTheme**](./clerk_flutter/clerk_theme.md) - Theming and customization + +--- + +## πŸš€ Quick Start + +### For Flutter Applications + +1. **Add dependencies** to your `pubspec.yaml`: + ```yaml + dependencies: + clerk_flutter: ^0.0.14-beta + ``` + +2. **Wrap your app** with `ClerkAuth`: + ```dart + MaterialApp( + builder: ClerkAuth.materialAppBuilder( + config: ClerkAuthConfig( + publishableKey: 'pk_test_...', + ), + ), + home: const HomePage(), + ) + ``` + +3. **Use pre-built UI** or build custom: + ```dart + // Pre-built UI + ClerkSignedOut( + child: const ClerkAuthentication(), + ) + + // Custom UI + ClerkAuthBuilder( + signedInBuilder: (context, authState) { + return Text('Welcome, ${authState.user?.fullName}!'); + }, + signedOutBuilder: (context, authState) { + return const ClerkAuthentication(); + }, + ) + ``` + +πŸ“– **[Full Flutter Guide β†’](./clerk_flutter/README.md)** + +### For Dart Applications + +1. **Add dependency** to your `pubspec.yaml`: + ```yaml + dependencies: + clerk_auth: ^0.0.14-beta + ``` + +2. **Initialize** the Auth instance: + ```dart + import 'package:clerk_auth/clerk_auth.dart'; + + final auth = Auth( + config: AuthConfig(publishableKey: 'pk_test_...'), + ); + await auth.initialize(); + ``` + +3. **Authenticate** users: + ```dart + // Email + Password sign-in + await auth.attemptSignIn(identifier: 'user@example.com'); + await auth.attemptSignIn(password: 'password123'); + + // Check auth state + if (auth.isSignedIn) { + print('User: ${auth.user?.fullName}'); + } + ``` + +πŸ“– **[Full Dart Guide β†’](./clerk_auth/README.md)** + +--- + +## πŸ“– Documentation Index + +### clerk_auth (Core SDK) + +| Document | Description | +|----------|-------------| +| [README](./clerk_auth/README.md) | Overview and getting started | +| [Auth](./clerk_auth/auth.md) | Complete API reference for authentication methods | +| [AuthConfig](./clerk_auth/auth_config.md) | Configuration options and initialization | +| [Persistor](./clerk_auth/persistor.md) | Storage interface for sessions and tokens | +| [HttpService](./clerk_auth/http_service.md) | HTTP client interface for custom networking | + +### clerk_flutter (Flutter Widgets) + +| Document | Description | +|----------|-------------| +| [README](./clerk_flutter/README.md) | Overview and getting started | +| [ClerkAuth](./clerk_flutter/clerk_auth.md) | Root widget and state management | +| [ClerkAuthBuilder](./clerk_flutter/clerk_auth_builder.md) | Builder pattern for custom UI | +| [ClerkAuthentication](./clerk_flutter/clerk_authentication.md) | Pre-built authentication UI | +| [ClerkUserButton](./clerk_flutter/clerk_user_button.md) | User profile button and menu | +| [ClerkOrganizationList](./clerk_flutter/clerk_organization_list.md) | Organization management UI | +| [ClerkSignedIn](./clerk_flutter/clerk_signed_in.md) | Conditional rendering (signed in) | +| [ClerkSignedOut](./clerk_flutter/clerk_signed_out.md) | Conditional rendering (signed out) | +| [ClerkErrorListener](./clerk_flutter/clerk_error_listener.md) | Error handling and display | +| [ClerkAuthConfig](./clerk_flutter/clerk_auth_config.md) | Flutter-specific configuration | +| [ClerkTheme](./clerk_flutter/clerk_theme.md) | Theming and customization | + +--- + +## 🎯 Common Use Cases + +### Authentication Flows + +- **[Email + Password Sign-In](./clerk_auth/auth.md#email--password-sign-in)** - Traditional authentication +- **[Email Code Sign-In](./clerk_auth/auth.md#email-code-sign-in)** - Passwordless with verification code +- **[Phone Code Sign-In](./clerk_auth/auth.md#phone-code-sign-in)** - SMS-based authentication +- **[OAuth Sign-In](./clerk_auth/auth.md#oauth-sign-in)** - Social login (Google, Apple, GitHub, etc.) +- **[Passkey Authentication](./clerk_auth/auth.md#passkey-authentication)** - WebAuthn/FIDO2 +- **[Multi-Factor Authentication](./clerk_auth/auth.md#multi-factor-authentication)** - 2FA with SMS or TOTP + +### Session Management + +- **[Session Handling](./clerk_auth/auth.md#session-management)** - Managing user sessions +- **[Multi-Session Support](./clerk_auth/auth.md#multi-session-management)** - Multiple accounts +- **[Token Management](./clerk_auth/auth.md#token-management)** - Access tokens and JWTs + +### User Management + +- **[User Profile](./clerk_auth/auth.md#user-management)** - Updating user information +- **[Email Management](./clerk_auth/auth.md#email-management)** - Adding/removing emails +- **[Phone Management](./clerk_auth/auth.md#phone-management)** - Adding/removing phone numbers +- **[External Accounts](./clerk_auth/auth.md#external-account-management)** - OAuth connections + +### Organization Features + +- **[Organization Management](./clerk_auth/auth.md#organization-management)** - Creating and managing orgs +- **[Organization Switching](./clerk_auth/auth.md#switching-organizations)** - Multi-org support +- **[Organization UI](./clerk_flutter/clerk_organization_list.md)** - Pre-built organization widgets + +--- + +## πŸ”‘ Key Concepts + +### Re-entrant Authentication + +The `attemptSignIn()` and `attemptSignUp()` methods are **re-entrant**, meaning you call them multiple times with different parameters to progress through the authentication flow: + +```dart +// Step 1: Provide identifier +await auth.attemptSignIn(identifier: 'user@example.com'); + +// Step 2: Provide password +await auth.attemptSignIn(password: 'password123'); + +// Step 3: If 2FA enabled, provide code +await auth.attemptSignIn(code: '123456'); +``` + +πŸ“– **[Learn more about re-entrant flows β†’](./clerk_auth/auth.md#re-entrant-methods)** + +### Transfer Flow + +When using OAuth or ID token authentication, Clerk uses a "transfer flow" to securely complete authentication: + +```dart +// 1. Initiate OAuth +final transfer = await auth.oauthSignIn(strategy: Strategy.oauth_google); + +// 2. Open browser to transfer.authUrl + +// 3. Complete after redirect +await auth.completeOAuthSignIn(transfer: transfer); +``` + +πŸ“– **[Learn more about transfer flow β†’](./clerk_auth/auth.md#transfer-flow)** + +--- + +## πŸ’‘ Best Practices + +1. **Use environment variables** for publishable keys +2. **Handle errors** with `ClerkErrorListener` (Flutter) or try-catch blocks (Dart) +3. **Respect re-entrant flows** - call `attemptSignIn`/`attemptSignUp` multiple times +4. **Check auth state** before accessing user/session data +5. **Implement proper loading states** during authentication +6. **Test all authentication strategies** enabled in your Clerk Dashboard +7. **Customize themes** to match your brand (Flutter) + +--- + +## πŸ†˜ Support + +- **Clerk Dashboard**: [https://dashboard.clerk.com](https://dashboard.clerk.com) +- **Clerk Documentation**: [https://clerk.com/docs](https://clerk.com/docs) +- **GitHub Issues**: Report bugs and request features + +--- + +*Documentation generated for Clerk SDK version 0.0.14-beta* + diff --git a/docs/clerk_auth/README.md b/docs/clerk_auth/README.md new file mode 100644 index 00000000..8609a7f1 --- /dev/null +++ b/docs/clerk_auth/README.md @@ -0,0 +1,301 @@ +# Clerk Auth Documentation + +Welcome to the comprehensive documentation for the `clerk_auth` package. This documentation covers all public APIs and provides detailed usage examples. + +## Documentation Index + +### Core Classes + +1. **[Auth](auth.md)** - The main authentication class + - All authentication methods (sign in, sign up, OAuth, passkeys) + - Session management + - User management + - Organization management + - **Special focus on re-entrant methods**: `attemptSignIn()` and `attemptSignUp()` + +2. **[AuthConfig](auth_config.md)** - Configuration for the Auth class + - Required and optional parameters + - Polling and refresh settings + - Telemetry configuration + - Complete configuration examples + +3. **[Persistor](persistor.md)** - Persistence interface for authentication state + - Abstract interface definition + - Built-in implementations (`Persistor.none`, `DefaultPersistor`) + - Custom implementation examples + - Storage keys used by Clerk + +4. **[HttpService](http_service.md)** - HTTP communication interface + - Abstract interface definition + - Default implementation + - Custom implementation examples (logging, retry, mocking) + - HTTP methods and request handling + +--- + +## Quick Start + +### Basic Setup + +```dart +import 'package:clerk_auth/clerk_auth.dart'; +import 'package:path_provider/path_provider.dart'; + +Future main() async { + // Create configuration + final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: DefaultPersistor( + getCacheDirectory: getApplicationDocumentsDirectory, + ), + ); + + // Create and initialize Auth + final auth = Auth(config: config); + await auth.initialize(); + + // Use auth for authentication + await auth.attemptSignIn( + strategy: Strategy.password, + identifier: 'user@example.com', + password: 'password123', + ); + + if (auth.isSignedIn) { + print('Welcome, ${auth.user?.fullName}!'); + } +} +``` + +--- + +## Key Concepts + +### Re-entrant Authentication Methods + +The `attemptSignIn()` and `attemptSignUp()` methods are designed to be **re-entrant**, meaning they can be called multiple times with different parameters as the user progresses through the authentication flow. + +**Example: Multi-step Sign In** +```dart +// Step 1: Start with email +await auth.attemptSignIn( + strategy: Strategy.emailCode, + identifier: 'user@example.com', +); + +// Step 2: Submit verification code +await auth.attemptSignIn( + strategy: Strategy.emailCode, + code: '123456', +); + +// Step 3: If 2FA required, submit 2FA code +if (auth.signIn?.needsSecondFactor == true) { + await auth.attemptSignIn( + strategy: Strategy.totp, + code: '654321', + ); +} +``` + +### Transfer Flow + +When using OAuth or ID token authentication, users may need to "transfer" between sign-in and sign-up flows: + +```dart +// Try sign in with Apple +await auth.idTokenSignIn( + provider: IdTokenProvider.apple, + idToken: credential.identityToken!, +); + +// If user doesn't exist, transfer to sign up +if (auth.signIn?.isTransferable == true) { + await auth.transfer(); +} +``` + +### State Management + +The Auth class maintains state through the `Client` object: +- `client.signIn` - Active sign-in flow +- `client.signUp` - Active sign-up flow +- `client.sessions` - Active sessions +- `session.user` - Current user + +--- + +## Common Use Cases + +### Email + Password Authentication + +```dart +// Sign up +await auth.attemptSignUp( + strategy: Strategy.emailCode, + emailAddress: 'user@example.com', + password: 'SecurePass123!', + passwordConfirmation: 'SecurePass123!', + firstName: 'John', + lastName: 'Doe', +); + +// Verify email +await auth.attemptSignUp( + strategy: Strategy.emailCode, + code: '123456', +); + +// Sign in +await auth.attemptSignIn( + strategy: Strategy.password, + identifier: 'user@example.com', + password: 'SecurePass123!', +); +``` + +### OAuth Authentication + +```dart +// Google Sign In +await auth.oauthSignIn( + strategy: Strategy.oauthGoogle, + redirect: Uri.parse('myapp://oauth-callback'), +); + +// After redirect, complete sign in +await auth.completeOAuthSignIn(token: rotatingTokenNonce); +``` + +### Passkey Authentication + +```dart +// Create passkey +final passkey = await auth.createPasskey(); +// ... use passkey library to register ... +await auth.attemptPasskeyVerification(passkey!, credentialJson); + +// Sign in with passkey +await auth.attemptSignIn(strategy: Strategy.passkey); +// ... use passkey library to authenticate ... +await auth.attemptSignIn( + strategy: Strategy.passkey, + passkeyCredential: credentialJson, +); +``` + +### Session Management + +```dart +// Get session token +final token = await auth.sessionToken(); +print('JWT: ${token.jwt}'); + +// Listen to token updates +auth.sessionTokenStream.listen((token) { + // Update your API client with new token +}); + +// Switch sessions +await auth.activate(anotherSession); + +// Sign out +await auth.signOut(); +``` + +--- + +## Architecture + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Auth β”‚ +β”‚ - Main authentication logic β”‚ +β”‚ - State management β”‚ +β”‚ - Public API β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β”‚ uses + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ β”‚ + β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ AuthConfig β”‚ β”‚ Api β”‚ +β”‚ β”‚ β”‚ (internal) β”‚ +β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β”‚ contains + β”‚ + β”Œβ”€β”€β”€β”΄β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ β”‚ β”‚ + β–Ό β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚Persisβ”‚ β”‚ Http β”‚ β”‚ Retry β”‚ +β”‚ tor β”‚ β”‚Service β”‚ β”‚ Options β”‚ +β””β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## Best Practices + +1. **Always initialize before use**: Call `auth.initialize()` before any other operations +2. **Use appropriate persistor**: `DefaultPersistor` for production, `Persistor.none` for testing +3. **Handle errors gracefully**: Wrap auth calls in try-catch or override `handleError()` +4. **Leverage re-entrant methods**: Call `attemptSignIn()`/`attemptSignUp()` multiple times as needed +5. **Check transfer status**: After OAuth/ID token auth, check `isTransferable` and call `transfer()` +6. **Clean up resources**: Call `auth.terminate()` when disposing +7. **Monitor session tokens**: Use `sessionTokenStream` to keep tokens fresh + +--- + +## Testing + +### Unit Testing + +```dart +import 'package:clerk_auth/clerk_auth.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('sign in test', () async { + final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: Persistor.none, + sessionTokenPolling: false, + clientRefreshPeriod: Duration.zero, + telemetryPeriod: Duration.zero, + ); + + final auth = Auth(config: config); + await auth.initialize(); + + // Test authentication flows + + auth.terminate(); + }); +} +``` + +--- + +## Additional Resources + +- [Clerk Dashboard](https://dashboard.clerk.com/) +- [Clerk API Reference](https://clerk.com/docs/reference/backend-api) +- [Flutter SDK Guide](https://clerk.com/docs/quickstarts/flutter) +- [clerk_auth on pub.dev](https://pub.dev/packages/clerk_auth) + +--- + +## Version + +*Documentation generated for clerk_auth version 0.0.14-beta* + +--- + +## Contributing + +Found an issue or want to improve the documentation? Please open an issue or pull request on the [GitHub repository](https://github.com/clerk/clerk-sdk-flutter). + diff --git a/docs/clerk_auth/auth.md b/docs/clerk_auth/auth.md new file mode 100644 index 00000000..818545df --- /dev/null +++ b/docs/clerk_auth/auth.md @@ -0,0 +1,1447 @@ +# Clerk Auth API Documentation + +This document provides comprehensive documentation for all public methods in the `Auth` class from `clerk_auth/lib/src/clerk_auth/auth.dart`. + +## Overview + +The `Auth` class is the core of the Clerk authentication system for Dart/Flutter applications. It provides a high-level API for managing user authentication, sessions, and user data. + +### Key Concepts + +**Re-entrant Methods**: The `attemptSignIn()` and `attemptSignUp()` methods are designed to be **re-entrant**, meaning they can be called multiple times with different parameters as the user progresses through the authentication flow. This design allows for flexible, step-by-step authentication processes. + +**State Management**: The Auth class maintains the current authentication state through the `Client` object, which contains: +- `SignIn` object (during sign-in flow) +- `SignUp` object (during sign-up flow) +- `User` object (when signed in) +- `Session` objects (active sessions) + +**Transfer Flow**: When using OAuth or ID token authentication, users may need to "transfer" between sign-in and sign-up flows if they don't exist yet (or already exist). Use the `transfer()` method to handle this seamlessly. + +## Table of Contents + +- [Initialization & Lifecycle](#initialization--lifecycle) +- [Authentication State](#authentication-state) +- [Sign In Methods](#sign-in-methods) +- [Sign Up Methods](#sign-up-methods) +- [OAuth Methods](#oauth-methods) +- [Passkey Methods](#passkey-methods) +- [Session Management](#session-management) +- [User Management](#user-management) +- [Organization Management](#organization-management) +- [Password Management](#password-management) +- [Advanced Methods](#advanced-methods) + +--- + +## Initialization & Lifecycle + +### `initialize()` + +Initializes the Auth object. **Must be called before any other Auth methods.** + +```dart +Future initialize() +``` + +**Example:** +```dart +final auth = Auth(config: AuthConfig( + publishableKey: 'pk_test_...', + persistor: Persistor.none, +)); +await auth.initialize(); +``` + +**Behavior:** +- Loads persisted client and environment data +- Sets up periodic client refresh (if configured) +- Starts session token polling (if enabled) +- Retries fetching client/environment if initial fetch fails + +--- + +### `terminate()` + +Disposes of the Auth object and cleans up resources. + +```dart +void terminate() +``` + +**Example:** +```dart +auth.terminate(); +``` + +**Behavior:** +- Cancels all timers (polling, refresh, etc.) +- Closes stream controllers +- Terminates telemetry and API connections + +--- + +## Authentication State + +### `isSignedIn` + +Returns whether a user is currently signed in. + +```dart +bool get isSignedIn +``` + +**Example:** +```dart +if (auth.isSignedIn) { + print('User: ${auth.user?.fullName}'); +} +``` + +--- + +### `isSigningIn` + +Returns whether a sign-in flow is currently in progress. + +```dart +bool get isSigningIn +``` + +--- + +### `isSigningUp` + +Returns whether a sign-up flow is currently in progress. + +```dart +bool get isSigningUp +``` + +--- + +### `client` + +The current Client object containing authentication state. + +```dart +Client get client +``` + +**Example:** +```dart +final client = auth.client; +print('Active sessions: ${client.sessions.length}'); +``` + +--- + +### `user` + +The currently signed-in User, or null if not signed in. + +```dart +User? get user +``` + +--- + +### `session` + +The current active Session, or null. + +```dart +Session? get session +``` + +--- + +### `signIn` + +The current SignIn object, or null. + +```dart +SignIn? get signIn +``` + +--- + +### `signUp` + +The current SignUp object, or null. + +```dart +SignUp? get signUp +``` + +--- + +### `organization` + +The current active Organization, or null. + +```dart +Organization? get organization +``` + +--- + +### `env` + +The Environment object containing Clerk account configuration. + +```dart +Environment get env +``` + +--- + +### `isNotAvailable` + +Returns whether the Auth object is not yet initialized. + +```dart +bool get isNotAvailable +``` + +**Example:** +```dart +if (auth.isNotAvailable) { + print('Auth is still initializing...'); +} +``` + +--- + +### `handleError()` + +Handles ClerkError exceptions when they occur. Override this method to customize error handling. + +```dart +void handleError(Object error) +``` + +**Default Behavior:** +- Throws the error + +**Example (Custom Error Handling):** +```dart +class MyAuth extends Auth { + MyAuth({required super.config}); + + @override + void handleError(Object error) { + if (error is ClerkError) { + // Log error instead of throwing + print('Clerk error: ${error.message}'); + // Show user-friendly message + showErrorToUser(error.message); + } else { + // Re-throw non-Clerk errors + super.handleError(error); + } + } +} +``` + +--- + +### `update()` + +A method to be overridden by extension classes to handle state updates. Called automatically after most Auth operations. + +```dart +void update() +``` + +**Example:** +```dart +class MyAuth extends Auth with ChangeNotifier { + MyAuth({required super.config}); + + @override + void update() { + super.update(); + notifyListeners(); // Notify UI of changes + } +} +``` + +--- + +## Sign In Methods + +### `attemptSignIn()` + +**Re-entrant method** for progressively signing in a user. Can be called multiple times with updated parameters until sign-in is complete. + +```dart +Future attemptSignIn({ + required Strategy strategy, + String? identifier, + String? password, + String? code, + String? token, + String? redirectUrl, + String? passkeyCredential, +}) +``` + +**Parameters:** +- `strategy`: The authentication strategy (e.g., `Strategy.emailCode`, `Strategy.password`) +- `identifier`: Email, phone, or username +- `password`: User's password (for password-based auth) +- `code`: Verification code (for code-based strategies) +- `token`: OAuth or ID token +- `redirectUrl`: Redirect URL for email link strategy +- `passkeyCredential`: Passkey credential JSON string + +**Example 1: Email + Password Sign In** +```dart +// Initial call with identifier and password +await auth.attemptSignIn( + strategy: Strategy.password, + identifier: 'user@example.com', + password: 'SecurePass123!', +); + +// If 2FA is required, call again with the code +if (auth.signIn?.needsSecondFactor == true) { + await auth.attemptSignIn( + strategy: Strategy.emailCode, + code: '123456', + ); +} +``` + +**Example 2: Email Code Sign In (Re-entrant)** +```dart +// Step 1: Start sign-in with email +await auth.attemptSignIn( + strategy: Strategy.emailCode, + identifier: 'user@example.com', +); + +// Step 2: User receives code, submit it +await auth.attemptSignIn( + strategy: Strategy.emailCode, + code: '123456', +); +``` + +**Example 3: Password Reset** +```dart +// Step 1: Initiate password reset +await auth.initiatePasswordReset( + identifier: 'user@example.com', + strategy: Strategy.resetPasswordEmailCode, +); + +// Step 2: Submit code and new password +await auth.attemptSignIn( + strategy: Strategy.resetPasswordEmailCode, + code: '123456', + password: 'NewSecurePass123!', +); +``` + +**Example 4: Passkey Sign In** +```dart +// Step 1: Prepare passkey sign-in +await auth.attemptSignIn(strategy: Strategy.passkey); + +// Step 2: Get passkey credential from authenticator +final nonce = auth.signIn?.firstFactorVerification?.nonce; +// ... use passkey library to get credential ... + +// Step 3: Complete sign-in with credential +await auth.attemptSignIn( + strategy: Strategy.passkey, + passkeyCredential: credentialJson, +); +``` + +**Re-entrant Behavior:** +- The method intelligently handles the current state of the sign-in flow +- Can be called multiple times as the user progresses through authentication steps +- Automatically creates a SignIn object if one doesn't exist +- Handles preparation and attempt phases based on the strategy and current state + +--- + +### `initiatePasswordReset()` + +Initiates a password reset flow. + +```dart +Future initiatePasswordReset({ + required String identifier, + required Strategy strategy, +}) +``` + +**Example:** +```dart +await auth.initiatePasswordReset( + identifier: 'user@example.com', + strategy: Strategy.resetPasswordEmailCode, +); +``` + +--- + +### `idTokenSignIn()` + +Sign in with an ID token from a provider (e.g., Apple, Google). + +```dart +Future idTokenSignIn({ + required IdTokenProvider provider, + required String idToken, +}) +``` + +**Example:** +```dart +// Apple Sign In +await auth.idTokenSignIn( + provider: IdTokenProvider.apple, + idToken: credential.identityToken!, +); + +// Check if transfer needed (user doesn't exist) +if (auth.signIn?.isTransferable == true) { + await auth.transfer(); // Switch to sign-up flow +} +``` + +**Transfer Flow:** +If the user doesn't exist, the verification status will be `transferable`. Call `transfer()` to switch to the sign-up flow. + +--- + +## Sign Up Methods + +### `attemptSignUp()` + +**Re-entrant method** for progressively signing up a new user. Can be called multiple times with updated parameters until sign-up is complete. + +```dart +Future attemptSignUp({ + required Strategy strategy, + String? firstName, + String? lastName, + String? username, + String? emailAddress, + String? phoneNumber, + String? password, + String? passwordConfirmation, + String? code, + String? token, + String? signature, + String? redirectUrl, + Map? metadata, + bool? legalAccepted, +}) +``` + +**Parameters:** +- `strategy`: The authentication strategy +- `firstName`, `lastName`, `username`: User profile information +- `emailAddress`, `phoneNumber`: Contact identifiers +- `password`, `passwordConfirmation`: Password (must match) +- `code`: Verification code +- `token`: OAuth or ID token +- `signature`: Email link signature +- `redirectUrl`: Redirect URL for email link strategy +- `metadata`: Custom metadata to attach to the user +- `legalAccepted`: Legal consent acceptance (required if enabled in settings) + +**Example 1: Email + Password Sign Up (Re-entrant)** +```dart +// Step 1: Create sign-up with initial data +await auth.attemptSignUp( + strategy: Strategy.emailCode, + emailAddress: 'newuser@example.com', + password: 'SecurePass123!', + passwordConfirmation: 'SecurePass123!', + firstName: 'John', + lastName: 'Doe', +); + +// Step 2: Verify email with code +await auth.attemptSignUp( + strategy: Strategy.emailCode, + code: '123456', +); +``` + +**Example 2: Phone Number Sign Up** +```dart +// Step 1: Create sign-up +await auth.attemptSignUp( + strategy: Strategy.phoneCode, + phoneNumber: '+1234567890', + firstName: 'Jane', + lastName: 'Smith', +); + +// Step 2: Verify phone +await auth.attemptSignUp( + strategy: Strategy.phoneCode, + code: '123456', +); +``` + +**Example 3: Sign Up with Metadata** +```dart +await auth.attemptSignUp( + strategy: Strategy.emailCode, + emailAddress: 'user@example.com', + password: 'SecurePass123!', + passwordConfirmation: 'SecurePass123!', + metadata: { + 'referralCode': 'ABC123', + 'source': 'mobile_app', + }, +); +``` + +**Example 4: Sign Up with Legal Consent** +```dart +await auth.attemptSignUp( + strategy: Strategy.emailCode, + emailAddress: 'user@example.com', + password: 'SecurePass123!', + passwordConfirmation: 'SecurePass123!', + legalAccepted: true, // Required if legal consent is enabled +); +``` + +**Re-entrant Behavior:** +- Can be called multiple times to update sign-up data +- Automatically handles verification preparation and attempts +- Updates existing SignUp object if one exists +- Creates new SignUp object on first call +- Validates password confirmation matches password + +**Throws:** +- `ClerkError` with code `passwordMatchError` if passwords don't match +- `ClerkError` with code `legalAcceptanceRequired` if legal consent is required but not provided + +--- + +### `idTokenSignUp()` + +Sign up with an ID token from a provider (e.g., Apple, Google). + +```dart +Future idTokenSignUp({ + required IdTokenProvider provider, + required String idToken, + String? firstName, + String? lastName, +}) +``` + +**Example:** +```dart +// Apple Sign Up +await auth.idTokenSignUp( + provider: IdTokenProvider.apple, + idToken: credential.identityToken!, + firstName: credential.givenName, + lastName: credential.familyName, +); + +// Check if transfer needed (user already exists) +if (auth.signUp?.isTransferable == true) { + await auth.transfer(); // Switch to sign-in flow +} +``` + +**Transfer Flow:** +If the user already exists, the verification status will be `transferable`. Call `transfer()` to switch to the sign-in flow. + +--- + +### `resendCode()` + +Resends a verification code for the given strategy. + +```dart +Future resendCode(Strategy strategy) +``` + +**Example:** +```dart +// During sign-up or sign-in +await auth.resendCode(Strategy.emailCode); +``` + +**Throws:** +- `ClerkError` with code `noInitialCodeHasBeenSetUpToResend` if no code flow is active + +--- + +## OAuth Methods + +### `oauthSignIn()` + +Prepares for sign-in via an OAuth provider. + +```dart +Future oauthSignIn({ + required Strategy strategy, + required Uri? redirect, +}) +``` + +**Example:** +```dart +await auth.oauthSignIn( + strategy: Strategy.oauthGoogle, + redirect: Uri.parse('myapp://oauth-callback'), +); + +// The user will be redirected to the OAuth provider +// After authentication, complete the flow with completeOAuthSignIn() +``` + +--- + +### `completeOAuthSignIn()` + +Completes OAuth sign-in by presenting the token. + +```dart +Future completeOAuthSignIn({ + required String token, +}) +``` + +**Example:** +```dart +// After OAuth redirect +await auth.completeOAuthSignIn(token: rotatingTokenNonce); +``` + +--- + +### `oauthConnect()` + +Connects an external account via an OAuth provider to the current user. + +```dart +Future oauthConnect({ + required Strategy strategy, + required Uri? redirect, +}) +``` + +**Example:** +```dart +// Connect Google account to existing user +await auth.oauthConnect( + strategy: Strategy.oauthGoogle, + redirect: Uri.parse('myapp://oauth-callback'), +); +``` + +--- + +### `deleteExternalAccount()` + +Deletes an external account connection. + +```dart +Future deleteExternalAccount({ + required ExternalAccount account, +}) +``` + +**Example:** +```dart +final googleAccount = auth.user?.externalAccounts + ?.firstWhere((acc) => acc.provider == 'google'); +if (googleAccount != null) { + await auth.deleteExternalAccount(account: googleAccount); +} +``` + +--- + +## Passkey Methods + +### `createPasskey()` + +Creates an unverified passkey for the current user. + +```dart +Future createPasskey() +``` + +**Example:** +```dart +final passkey = await auth.createPasskey(); +if (passkey?.verification?.nonce case VerificationNonce nonce) { + // Use passkey library to register + final authenticator = PasskeyAuthenticator(); + final challenge = RegisterRequestType( + challenge: nonce.challenge, + relyingParty: nonce.relyingParty.toRelyingPartyType(), + user: nonce.user!.toUserType(), + excludeCredentials: const [], + timeout: nonce.timeout, + ); + final credential = await authenticator.register(challenge); + + // Verify the passkey + await auth.attemptPasskeyVerification(passkey!, credential.toJsonString()); +} +``` + +--- + +### `attemptPasskeyVerification()` + +Verifies a passkey with the provided credential. + +```dart +Future attemptPasskeyVerification( + Passkey passkey, + String credential, +) +``` + +**Example:** +```dart +await auth.attemptPasskeyVerification(passkey, credentialJson); +``` + +--- + +## Session Management + +### `sessionToken()` + +Gets the current session token for an organization. + +```dart +Future sessionToken({ + Organization? organization, + String? templateName, +}) +``` + +**Example:** +```dart +// Get default session token +final token = await auth.sessionToken(); +print('JWT: ${token.jwt}'); + +// Get token for specific organization +final orgToken = await auth.sessionToken( + organization: myOrganization, +); + +// Get token with custom template +final customToken = await auth.sessionToken( + templateName: 'my_custom_template', +); +``` + +**Throws:** +- `ClerkError` with code `noSessionTokenRetrieved` if token cannot be retrieved + +--- + +### `sessionTokenStream` + +Stream of SessionTokens as they renew. + +```dart +Stream get sessionTokenStream +``` + +**Example:** +```dart +auth.sessionTokenStream.listen((token) { + print('New token: ${token.jwt}'); + // Update your API client with new token +}); +``` + +--- + +### `activate()` + +Activates the given session. + +```dart +Future activate(Session session) +``` + +**Example:** +```dart +final sessions = auth.client.sessions; +if (sessions.length > 1) { + await auth.activate(sessions[1]); // Switch to another session +} +``` + +--- + +### `signOut()` + +Signs out of all sessions and deletes the current client. + +```dart +Future signOut() +``` + +**Example:** +```dart +await auth.signOut(); +``` + +--- + +### `signOutOf()` + +Signs out of a specific session. + +```dart +Future signOutOf(Session session) +``` + +**Example:** +```dart +await auth.signOutOf(auth.session!); +``` + +--- + +### `transfer()` + +Transfers an OAuth authentication into a User (handles sign-in/sign-up transfer flow). + +```dart +Future transfer() +``` + +**Example:** +```dart +// After OAuth or ID token authentication +if (auth.signIn?.isTransferable == true) { + await auth.transfer(); // Switch to sign-up +} else if (auth.signUp?.isTransferable == true) { + await auth.transfer(); // Switch to sign-in +} +``` + +--- + +## User Management + +### `updateUser()` + +Updates the current user's profile information. + +```dart +Future updateUser({ + String? username, + String? firstName, + String? lastName, + String? primaryEmailAddressId, + String? primaryPhoneNumberId, + String? primaryWeb3WalletId, + Map? metadata, + File? avatar, +}) +``` + +**Example:** +```dart +await auth.updateUser( + firstName: 'John', + lastName: 'Doe', + username: 'johndoe', + metadata: {'theme': 'dark'}, +); +``` + +--- + +### `deleteUser()` + +Deletes the current user. + +```dart +Future deleteUser() +``` + +**Example:** +```dart +if (auth.env.user.actions.deleteSelf) { + await auth.deleteUser(); +} +``` + +**Throws:** +- `ClerkError` with code `cannotDeleteSelf` if user is not authorized to delete themselves + +--- + +### `updateUserImage()` + +Updates the user's avatar. + +```dart +Future updateUserImage(File file) +``` + +**Example:** +```dart +final imageFile = File('/path/to/avatar.jpg'); +await auth.updateUserImage(imageFile); +``` + +--- + +### `deleteUserImage()` + +Deletes the user's avatar. + +```dart +Future deleteUserImage() +``` + +**Example:** +```dart +await auth.deleteUserImage(); +``` + +--- + +### `addIdentifyingData()` + +Adds an email, phone number, or other identifier to the current user. + +```dart +Future addIdentifyingData( + String identifier, + IdentifierType type, +) +``` + +**Example:** +```dart +// Add email address +await auth.addIdentifyingData( + 'newemail@example.com', + IdentifierType.emailAddress, +); + +// Add phone number +await auth.addIdentifyingData( + '+1234567890', + IdentifierType.phoneNumber, +); +``` + +--- + +### `verifyIdentifyingData()` + +Verifies user identifying data with a code. + +```dart +Future verifyIdentifyingData( + UserIdentifyingData uid, + String code, +) +``` + +**Example:** +```dart +final email = auth.user?.emailAddresses + ?.firstWhere((e) => e.emailAddress == 'newemail@example.com'); +if (email != null) { + await auth.verifyIdentifyingData(email, '123456'); +} +``` + +--- + +### `deleteIdentifyingData()` + +Deletes user identifying data. + +```dart +Future deleteIdentifyingData( + UserIdentifyingData uid, +) +``` + +**Example:** +```dart +final email = auth.user?.emailAddresses?.first; +if (email != null) { + await auth.deleteIdentifyingData(email); +} +``` + +--- + +## Password Management + +### `updateUserPassword()` + +Updates the current user's password. + +```dart +Future updateUserPassword( + String currentPassword, + String newPassword, { + bool signOut = true, +}) +``` + +**Example:** +```dart +await auth.updateUserPassword( + 'OldPassword123!', + 'NewPassword456!', + signOut: false, // Keep user signed in +); +``` + +--- + +### `deleteUserPassword()` + +Deletes the current user's password. + +```dart +Future deleteUserPassword(String currentPassword) +``` + +**Example:** +```dart +await auth.deleteUserPassword('CurrentPassword123!'); +``` + +--- + +## Organization Management + +### `createOrganization()` + +Creates a new organization. + +```dart +Future createOrganization({ + required String name, + String? slug, + File? logo, +}) +``` + +**Example:** +```dart +await auth.createOrganization( + name: 'Acme Corp', + slug: 'acme-corp', + logo: File('/path/to/logo.png'), +); +``` + +--- + +### `updateOrganization()` + +Updates an organization. + +```dart +Future updateOrganization({ + required Organization organization, + String? name, + File? logo, +}) +``` + +**Example:** +```dart +await auth.updateOrganization( + organization: auth.organization!, + name: 'New Name', + logo: File('/path/to/new-logo.png'), +); +``` + +--- + +### `setActiveOrganization()` + +Makes an organization active. + +```dart +Future setActiveOrganization(Organization organization) +``` + +**Example:** +```dart +final orgs = auth.user?.organizationMemberships; +if (orgs != null && orgs.isNotEmpty) { + await auth.setActiveOrganization(orgs.first.organization); +} +``` + +--- + +### `leaveOrganization()` + +Leaves an organization. + +```dart +Future leaveOrganization({ + required Organization organization, + Session? session, +}) +``` + +**Example:** +```dart +final success = await auth.leaveOrganization( + organization: auth.organization!, +); +``` + +--- + +### `fetchOrganizationInvitations()` + +Fetches all organization invitations for the user. + +```dart +Future> fetchOrganizationInvitations() +``` + +**Example:** +```dart +final invitations = await auth.fetchOrganizationInvitations(); +for (final invitation in invitations) { + print('Invited to: ${invitation.organizationName}'); +} +``` + +--- + +### `acceptOrganizationInvitation()` + +Accepts an organization invitation. + +```dart +Future acceptOrganizationInvitation( + OrganizationInvitation invitation, +) +``` + +**Example:** +```dart +final invitations = await auth.fetchOrganizationInvitations(); +if (invitations.isNotEmpty) { + await auth.acceptOrganizationInvitation(invitations.first); +} +``` + +--- + +### `fetchOrganizationDomains()` + +Fetches all domains for an organization. + +```dart +Future> fetchOrganizationDomains({ + required Organization organization, +}) +``` + +**Example:** +```dart +final domains = await auth.fetchOrganizationDomains( + organization: auth.organization!, +); +``` + +--- + +### `createDomain()` + +Creates a new domain within an organization. + +```dart +Future createDomain({ + required Organization organization, + required String name, + required EnrollmentMode mode, +}) +``` + +**Example:** +```dart +await auth.createDomain( + organization: auth.organization!, + name: 'example.com', + mode: EnrollmentMode.automaticInvitation, +); +``` + +--- + +## Advanced Methods + +### `refreshClient()` + +Refreshes the current client from the server. + +```dart +Future refreshClient() +``` + +**Example:** +```dart +await auth.refreshClient(); +``` + +--- + +### `resetClient()` + +Resets the current client, clearing any SignUp or SignIn objects. + +```dart +Future resetClient() +``` + +**Example:** +```dart +await auth.resetClient(); +``` + +--- + +### `refreshEnvironment()` + +Refreshes the current environment from the server. + +```dart +Future refreshEnvironment() +``` + +**Example:** +```dart +await auth.refreshEnvironment(); +``` + +--- + +### `fetchApiResponse()` + +Low-level access to the Clerk API. + +```dart +Future fetchApiResponse( + String url, { + HttpMethod method = HttpMethod.post, + Map? headers, + Map? params, + Map? nullableParams, + bool withSession = false, +}) +``` + +**Example:** +```dart +final response = await auth.fetchApiResponse( + '/users/me/custom_endpoint', + method: HttpMethod.get, + withSession: true, +); +``` + +**Note:** This method will be deprecated in a future version. Use only for advanced use cases not covered by other methods. + +--- + +## Error Handling + +All methods may throw `ClerkError` exceptions. Use try-catch blocks or override the `handleError` method to handle errors. + +**Example:** +```dart +try { + await auth.attemptSignIn( + strategy: Strategy.password, + identifier: 'user@example.com', + password: 'wrong_password', + ); +} on ClerkError catch (error) { + print('Error: ${error.message}'); + print('Code: ${error.code}'); +} +``` + +**Custom Error Handling:** +```dart +class MyAuth extends Auth { + MyAuth({required super.config}); + + @override + void handleError(Object error) { + // Custom error handling + if (error is ClerkError) { + print('Clerk error: ${error.message}'); + } + // Don't call super.handleError() to prevent throwing + } +} +``` + +--- + +## Common Patterns + +### Complete Sign-In Flow + +```dart +// Email + Password with optional 2FA +Future signInUser(String email, String password) async { + await auth.attemptSignIn( + strategy: Strategy.password, + identifier: email, + password: password, + ); + + // Check if 2FA is required + if (auth.signIn?.needsSecondFactor == true) { + // Prompt user for 2FA code + final code = await promptUserFor2FACode(); + await auth.attemptSignIn( + strategy: Strategy.emailCode, + code: code, + ); + } + + // User is now signed in + print('Welcome, ${auth.user?.fullName}!'); +} +``` + +### Complete Sign-Up Flow + +```dart +// Email + Password sign-up with verification +Future signUpUser({ + required String email, + required String password, + required String firstName, + required String lastName, +}) async { + // Step 1: Create sign-up + await auth.attemptSignUp( + strategy: Strategy.emailCode, + emailAddress: email, + password: password, + passwordConfirmation: password, + firstName: firstName, + lastName: lastName, + ); + + // Step 2: Verify email + final code = await promptUserForEmailCode(); + await auth.attemptSignUp( + strategy: Strategy.emailCode, + code: code, + ); + + // User is now signed up and signed in + print('Welcome, ${auth.user?.fullName}!'); +} +``` + +### OAuth Sign-In with Transfer + +```dart +// Apple Sign In with automatic transfer +Future appleSignIn(String idToken, {String? firstName, String? lastName}) async { + // Try sign-in first + await auth.idTokenSignIn( + provider: IdTokenProvider.apple, + idToken: idToken, + ); + + // If user doesn't exist, transfer to sign-up + if (auth.signIn?.isTransferable == true) { + await auth.transfer(); + + // Update with additional info if available + if (firstName != null || lastName != null) { + await auth.attemptSignUp( + strategy: Strategy.oauthTokenApple, + firstName: firstName, + lastName: lastName, + ); + } + } + + print('Signed in: ${auth.user?.fullName}'); +} +``` + +### Multi-Session Management + +```dart +// Switch between multiple sessions +Future switchSession(int sessionIndex) async { + final sessions = auth.client.sessions; + if (sessionIndex < sessions.length) { + await auth.activate(sessions[sessionIndex]); + print('Switched to: ${auth.user?.fullName}'); + } +} + +// Sign out of specific session +Future signOutSession(Session session) async { + await auth.signOutOf(session); + print('Signed out of session: ${session.id}'); +} +``` + +--- + +## Best Practices + +1. **Always initialize before use**: Call `initialize()` before any other Auth methods +2. **Handle errors gracefully**: Wrap Auth calls in try-catch blocks or override `handleError()` +3. **Use re-entrant methods correctly**: `attemptSignIn()` and `attemptSignUp()` are designed to be called multiple times +4. **Check transfer status**: After OAuth/ID token auth, check `isTransferable` and call `transfer()` if needed +5. **Clean up resources**: Call `terminate()` when disposing of the Auth object +6. **Monitor session tokens**: Use `sessionTokenStream` to keep your API client updated with fresh tokens +7. **Validate passwords**: Ensure password and passwordConfirmation match before calling `attemptSignUp()` +8. **Check environment settings**: Use `env` to check what features are enabled before attempting operations + +--- + +## Related Documentation + +- [Clerk Dashboard](https://dashboard.clerk.com/) +- [Clerk API Reference](https://clerk.com/docs/reference/backend-api) +- [Flutter SDK Guide](https://clerk.com/docs/quickstarts/flutter) + +--- + +*Generated for clerk_auth version 0.0.14-beta* + + diff --git a/docs/clerk_auth/auth_config.md b/docs/clerk_auth/auth_config.md new file mode 100644 index 00000000..9a466c29 --- /dev/null +++ b/docs/clerk_auth/auth_config.md @@ -0,0 +1,520 @@ +# AuthConfig Documentation + +This document provides comprehensive documentation for the `AuthConfig` class from `clerk_auth/lib/src/clerk_auth/auth_config.dart`. + +## Overview + +The `AuthConfig` class holds all configurable items required for the `Auth` class, with sensible defaults. It is the primary configuration object for initializing Clerk authentication in your application. + +## Class Definition + +```dart +class AuthConfig { + const AuthConfig({ + required this.publishableKey, + required this.persistor, + this.flags = const SdkFlags(), + this.sessionTokenPolling = true, + this.retryOptions = const RetryOptions(), + this.defaultSessionTokenTemplate, + LocalesLookup? localesLookup, + bool? isTestMode, + String? telemetryEndpoint, + Duration? telemetryPeriod, + Duration? clientRefreshPeriod, + Duration? httpConnectionTimeout, + HttpService? httpService, + }); +} +``` + +--- + +## Required Parameters + +### `publishableKey` + +**Type:** `String` + +The publishable key from your Clerk dashboard that identifies your auth service account. + +**Example:** +```dart +final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: myPersistor, +); +``` + +**Where to find it:** +1. Go to [Clerk Dashboard](https://dashboard.clerk.com/) +2. Select your application +3. Navigate to API Keys +4. Copy the "Publishable Key" + +--- + +### `persistor` + +**Type:** `Persistor` + +The persistor used for storing authentication state between app sessions. + +**Example:** +```dart +// Using DefaultPersistor +final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: DefaultPersistor( + getCacheDirectory: () => getApplicationDocumentsDirectory(), + ), +); + +// Using no persistence (testing only) +final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: Persistor.none, +); +``` + +**See also:** [Persistor Documentation](persistor.md) + +--- + +## Optional Parameters + +### `flags` + +**Type:** `SdkFlags` +**Default:** `const SdkFlags()` + +Flags used to affect SDK behavior. Extended by `clerk_flutter` for Flutter-specific flags. + +**Example:** +```dart +final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: myPersistor, + flags: const SdkFlags(), +); +``` + +--- + +### `sessionTokenPolling` + +**Type:** `bool` +**Default:** `true` + +Whether to regularly poll for new session tokens. When enabled, the SDK automatically refreshes session tokens before they expire. + +**Example:** +```dart +// Enable automatic token refresh (default) +final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: myPersistor, + sessionTokenPolling: true, +); + +// Disable automatic token refresh +final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: myPersistor, + sessionTokenPolling: false, +); +``` + +**When to disable:** +- Testing scenarios where you want manual control +- Applications that don't need long-lived sessions + +--- + +### `retryOptions` + +**Type:** `RetryOptions` +**Default:** `const RetryOptions()` + +Options for retrying failed HTTP requests. Uses the `retry` package. + +**Example:** +```dart +import 'package:retry/retry.dart'; + +final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: myPersistor, + retryOptions: RetryOptions( + maxAttempts: 5, + delayFactor: Duration(milliseconds: 200), + maxDelay: Duration(seconds: 10), + ), +); +``` + +**RetryOptions properties:** +- `maxAttempts`: Maximum number of retry attempts (default: 8) +- `delayFactor`: Initial delay between retries (default: 200ms) +- `maxDelay`: Maximum delay between retries (default: 30s) +- `randomizationFactor`: Randomization factor for delays (default: 0.25) + +--- + +### `defaultSessionTokenTemplate` + +**Type:** `String?` +**Default:** `null` + +Default template name for session token retrieval. Useful when using custom JWT templates. + +**Example:** +```dart +final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: myPersistor, + defaultSessionTokenTemplate: 'my_custom_template', +); +``` + +**Use case:** +When you have custom claims or need specific JWT structure for your backend API. + +--- + +### `localesLookup` + +**Type:** `LocalesLookup` (function: `List Function()`) +**Default:** `Auth.defaultLocalesLookup` (returns `['en']`) + +Function to return the current user's locale preferences for translations. + +**Example:** +```dart +final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: myPersistor, + localesLookup: () { + // Return user's preferred locales + return ['es', 'en']; // Spanish first, English fallback + }, +); +``` + +--- + +### `isTestMode` + +**Type:** `bool` +**Default:** `false` + +Whether the SDK is running in test mode. + +**Example:** +```dart +final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: myPersistor, + isTestMode: true, +); +``` + +--- + +### `telemetryEndpoint` + +**Type:** `String` +**Default:** `'https://clerk-telemetry.com/v1/event'` + +The endpoint to send telemetry data to. + +**Example:** +```dart +final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: myPersistor, + telemetryEndpoint: 'https://my-custom-telemetry.com/events', +); +``` + +--- + +### `telemetryPeriod` + +**Type:** `Duration` +**Default:** `Duration(milliseconds: 29300)` (~30 seconds) + +The duration between sends of telemetry data. Set to `Duration.zero` to disable telemetry. + +**Example:** +```dart +// Custom telemetry interval +final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: myPersistor, + telemetryPeriod: Duration(minutes: 1), +); + +// Disable telemetry +final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: myPersistor, + telemetryPeriod: Duration.zero, +); +``` + +**Note:** The default is slightly offset from 30s to avoid repeated clashes with other regular tasks. + +--- + +### `clientRefreshPeriod` + +**Type:** `Duration` +**Default:** `Duration(milliseconds: 9700)` (~10 seconds) + +The duration between calls to refresh the client object. Set to `Duration.zero` to disable automatic client refresh. + +**Example:** +```dart +// Custom refresh interval +final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: myPersistor, + clientRefreshPeriod: Duration(seconds: 30), +); + +// Disable automatic refresh +final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: myPersistor, + clientRefreshPeriod: Duration.zero, +); +``` + +**Note:** The default is slightly offset from 10s to avoid repeated clashes with other regular tasks. + +**When to adjust:** +- Increase for battery-sensitive applications +- Decrease for applications requiring real-time updates +- Disable for complete manual control + +--- + +### `httpConnectionTimeout` + +**Type:** `Duration` +**Default:** `Duration(milliseconds: 500)` + +The duration to wait for HTTP connectivity before timing out in a connectivity test. + +**Example:** +```dart +final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: myPersistor, + httpConnectionTimeout: Duration(seconds: 2), +); +``` + +--- + +### `httpService` + +**Type:** `HttpService` +**Default:** `DefaultHttpService()` + +The HTTP service used to communicate with the Clerk backend. + +**Example:** +```dart +// Using default HTTP service +final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: myPersistor, + httpService: DefaultHttpService(), +); + +// Using custom HTTP service +class MyHttpService implements HttpService { + // ... implement interface +} + +final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: myPersistor, + httpService: MyHttpService(), +); +``` + +**See also:** [HttpService Documentation](http_service.md) + +--- + +## Methods + +### `initialize()` + +Initializes the configuration by initializing the persistor and HTTP service. + +```dart +Future initialize() +``` + +**Example:** +```dart +final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: myPersistor, +); +await config.initialize(); +``` + +**Note:** This is typically called automatically by `Auth.initialize()`. + +--- + +### `terminate()` + +Terminates the configuration by terminating the HTTP service and persistor. + +```dart +void terminate() +``` + +**Example:** +```dart +config.terminate(); +``` + +**Note:** This is typically called automatically by `Auth.terminate()`. + +--- + +## Complete Examples + +### Basic Configuration + +```dart +import 'package:clerk_auth/clerk_auth.dart'; +import 'package:path_provider/path_provider.dart'; + +Future createBasicConfig() async { + return AuthConfig( + publishableKey: 'pk_test_Y2xlcmsuZXhhbXBsZS5jb20k', + persistor: DefaultPersistor( + getCacheDirectory: getApplicationDocumentsDirectory, + ), + ); +} +``` + +### Production Configuration + +```dart +import 'package:clerk_auth/clerk_auth.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:retry/retry.dart'; + +Future createProductionConfig() async { + return AuthConfig( + publishableKey: 'pk_live_...', + persistor: DefaultPersistor( + getCacheDirectory: getApplicationDocumentsDirectory, + ), + sessionTokenPolling: true, + retryOptions: RetryOptions( + maxAttempts: 5, + delayFactor: Duration(milliseconds: 200), + ), + clientRefreshPeriod: Duration(seconds: 15), + telemetryPeriod: Duration(seconds: 60), + ); +} +``` + +### Testing Configuration + +```dart +import 'package:clerk_auth/clerk_auth.dart'; + +AuthConfig createTestConfig() { + return AuthConfig( + publishableKey: 'pk_test_...', + persistor: Persistor.none, // No persistence in tests + isTestMode: true, + sessionTokenPolling: false, + clientRefreshPeriod: Duration.zero, + telemetryPeriod: Duration.zero, + ); +} +``` + +### Custom Locale Configuration + +```dart +import 'package:clerk_auth/clerk_auth.dart'; +import 'package:flutter/material.dart'; + +AuthConfig createLocalizedConfig(BuildContext context) { + return AuthConfig( + publishableKey: 'pk_test_...', + persistor: myPersistor, + localesLookup: () { + final locale = Localizations.localeOf(context); + return [locale.languageCode, 'en']; // User's locale + English fallback + }, + ); +} +``` + +### Performance-Optimized Configuration + +```dart +import 'package:clerk_auth/clerk_auth.dart'; + +AuthConfig createOptimizedConfig() { + return AuthConfig( + publishableKey: 'pk_test_...', + persistor: myPersistor, + // Reduce polling frequency for battery savings + clientRefreshPeriod: Duration(seconds: 30), + telemetryPeriod: Duration(minutes: 5), + // Faster timeout for better UX + httpConnectionTimeout: Duration(milliseconds: 300), + ); +} +``` + +--- + +## Best Practices + +1. **Always use a Persistor in production**: Use `DefaultPersistor` or a custom implementation. Only use `Persistor.none` for testing. + +2. **Secure your publishable key**: Store it in environment variables or secure configuration, not hardcoded in source. + +3. **Adjust polling based on use case**: + - Real-time apps: Shorter `clientRefreshPeriod` + - Battery-sensitive apps: Longer periods or disable polling + - Background apps: Disable polling and refresh manually + +4. **Configure retry options for reliability**: Adjust based on your network conditions and user experience requirements. + +5. **Use custom session token templates**: When you need specific JWT claims for your backend API. + +6. **Disable telemetry in development**: Set `telemetryPeriod` to `Duration.zero` during development if desired. + +7. **Test with different configurations**: Create separate configs for development, staging, and production. + +--- + +## Related Documentation + +- [Auth Documentation](auth.md) +- [Persistor Documentation](persistor.md) +- [HttpService Documentation](http_service.md) +- [Clerk Dashboard](https://dashboard.clerk.com/) + +--- + +*Generated for clerk_auth version 0.0.14-beta* diff --git a/docs/clerk_auth/http_service.md b/docs/clerk_auth/http_service.md new file mode 100644 index 00000000..b6785e44 --- /dev/null +++ b/docs/clerk_auth/http_service.md @@ -0,0 +1,657 @@ +# HttpService Documentation + +This document provides comprehensive documentation for the `HttpService` interface and its implementations from `clerk_auth/lib/src/clerk_auth/http_service.dart`. + +## Overview + +The `HttpService` abstract interface defines how the Clerk SDK communicates with the Clerk backend over HTTP. It provides methods for sending requests, uploading files, and checking connectivity. + +## HttpMethod Enum + +```dart +enum HttpMethod { + delete, + get, + patch, + post, + put, +} +``` + +**Methods:** +- `delete`: HTTP DELETE +- `get`: HTTP GET +- `patch`: HTTP PATCH +- `post`: HTTP POST +- `put`: HTTP PUT + +**Properties:** +- `isGet`: Returns `true` if method is GET +- `isNotGet`: Returns `true` if method is not GET +- `toString()`: Returns uppercase method name (e.g., "GET", "POST") + +**Example:** +```dart +final method = HttpMethod.post; +print(method.toString()); // "POST" +print(method.isGet); // false +print(method.isNotGet); // true +``` + +--- + +## Abstract Interface + +```dart +abstract interface class HttpService { + const HttpService(); + + Future initialize(); + void terminate(); + Future ping(Uri uri, {required Duration timeout}); + Future send( + HttpMethod method, + Uri uri, { + Map? headers, + Map? params, + String? body, + }); + Future sendByteStream( + HttpMethod method, + Uri uri, + http.ByteStream byteStream, + int length, + Map headers, + ); +} +``` + +--- + +## Methods + +### `initialize()` + +Initializes the HTTP service. May be called multiple times and must handle that gracefully. + +```dart +Future initialize() +``` + +**Example:** +```dart +final httpService = DefaultHttpService(); +await httpService.initialize(); +``` + +**Note:** The default implementation is a no-op. + +--- + +### `terminate()` + +Terminates the HTTP service and cleans up resources. May be called multiple times and must handle that gracefully. + +```dart +void terminate() +``` + +**Example:** +```dart +httpService.terminate(); +``` + +--- + +### `ping()` + +Checks that connectivity to an endpoint is available. + +```dart +Future ping(Uri uri, {required Duration timeout}) +``` + +**Parameters:** +- `uri`: The endpoint to ping +- `timeout`: Maximum time to wait for response + +**Returns:** +- `true` if endpoint is reachable and returns 200 status +- `false` otherwise + +**Example:** +```dart +final isReachable = await httpService.ping( + Uri.parse('https://api.clerk.com'), + timeout: Duration(seconds: 5), +); +if (isReachable) { + print('API is reachable'); +} +``` + +--- + +### `send()` + +Sends an HTTP request to the backend and receives a response. + +```dart +Future send( + HttpMethod method, + Uri uri, { + Map? headers, + Map? params, + String? body, +}) +``` + +**Parameters:** +- `method`: The HTTP method to use +- `uri`: The endpoint URI +- `headers`: Optional HTTP headers +- `params`: Optional form parameters (converted to body fields) +- `body`: Optional request body (raw string) + +**Returns:** +- `http.Response` from the server + +**Example:** +```dart +final response = await httpService.send( + HttpMethod.post, + Uri.parse('https://api.clerk.com/v1/client'), + headers: { + 'Authorization': 'Bearer token', + 'Content-Type': 'application/json', + }, + body: jsonEncode({'key': 'value'}), +); + +print('Status: ${response.statusCode}'); +print('Body: ${response.body}'); +``` + +**With params:** +```dart +final response = await httpService.send( + HttpMethod.post, + Uri.parse('https://api.clerk.com/v1/sign_in'), + params: { + 'identifier': 'user@example.com', + 'password': 'password123', + }, +); +``` + +--- + +### `sendByteStream()` + +Uploads a file to the backend using a multipart request. + +```dart +Future sendByteStream( + HttpMethod method, + Uri uri, + http.ByteStream byteStream, + int length, + Map headers, +) +``` + +**Parameters:** +- `method`: The HTTP method to use +- `uri`: The endpoint URI +- `byteStream`: The file data as a byte stream +- `length`: The length of the byte stream +- `headers`: HTTP headers + +**Returns:** +- `http.Response` from the server + +**Example:** +```dart +import 'dart:io'; +import 'package:http/http.dart' as http; + +final file = File('/path/to/avatar.jpg'); +final byteStream = http.ByteStream(file.openRead()); +final length = await file.length(); + +final response = await httpService.sendByteStream( + HttpMethod.post, + Uri.parse('https://api.clerk.com/v1/me/profile_image'), + byteStream, + length, + { + 'Authorization': 'Bearer token', + }, +); +``` + +--- + +## DefaultHttpService + +The default implementation of `HttpService` using the `http` package. + +### Constructor + +```dart +const DefaultHttpService() +``` + +**Example:** +```dart +final httpService = DefaultHttpService(); +``` + +### Behavior + +- Uses a single `http.Client` instance per `DefaultHttpService` instance +- Automatically manages client lifecycle +- Cleans up client on `terminate()` +- Thread-safe client management + +### Implementation Details + +**Client Management:** +```dart +static final _clients = {}; +http.Client get _client => _clients[this] ??= http.Client(); +``` + +**Ping Implementation:** +- Uses HTTP HEAD request +- Returns `true` only if status code is 200 +- Catches all exceptions and returns `false` + +**Send Implementation:** +- Creates `http.Request` with method and URI +- Adds headers if provided +- Converts params to body fields if provided +- Sets body if provided +- Sends request and converts streamed response to regular response + +**SendByteStream Implementation:** +- Creates `http.MultipartRequest` +- Adds file as multipart file with field name "file" +- Uses hash code as filename +- Sends request and converts streamed response + +--- + +## Custom Implementation + +You can create custom HTTP service implementations for specific requirements. + +### Example: Logging HTTP Service + +```dart +class LoggingHttpService implements HttpService { + final HttpService _delegate; + + LoggingHttpService(this._delegate); + + @override + Future initialize() => _delegate.initialize(); + + @override + void terminate() => _delegate.terminate(); + + @override + Future ping(Uri uri, {required Duration timeout}) async { + print('Pinging: $uri'); + final result = await _delegate.ping(uri, timeout: timeout); + print('Ping result: $result'); + return result; + } + + @override + Future send( + HttpMethod method, + Uri uri, { + Map? headers, + Map? params, + String? body, + }) async { + print('Sending $method to $uri'); + final response = await _delegate.send( + method, + uri, + headers: headers, + params: params, + body: body, + ); + print('Response: ${response.statusCode}'); + return response; + } + + @override + Future sendByteStream( + HttpMethod method, + Uri uri, + http.ByteStream byteStream, + int length, + Map headers, + ) async { + print('Uploading file to $uri (${length} bytes)'); + final response = await _delegate.sendByteStream( + method, + uri, + byteStream, + length, + headers, + ); + print('Upload response: ${response.statusCode}'); + return response; + } +} +``` + +### Example: Retry HTTP Service + +```dart +import 'package:retry/retry.dart'; + +class RetryHttpService implements HttpService { + final HttpService _delegate; + final RetryOptions _retryOptions; + + RetryHttpService(this._delegate, { + RetryOptions? retryOptions, + }) : _retryOptions = retryOptions ?? const RetryOptions(); + + @override + Future initialize() => _delegate.initialize(); + + @override + void terminate() => _delegate.terminate(); + + @override + Future ping(Uri uri, {required Duration timeout}) { + return _delegate.ping(uri, timeout: timeout); + } + + @override + Future send( + HttpMethod method, + Uri uri, { + Map? headers, + Map? params, + String? body, + }) async { + return _retryOptions.retry( + () => _delegate.send( + method, + uri, + headers: headers, + params: params, + body: body, + ), + retryIf: (e) => e is SocketException || e is TimeoutException, + ); + } + + @override + Future sendByteStream( + HttpMethod method, + Uri uri, + http.ByteStream byteStream, + int length, + Map headers, + ) async { + return _retryOptions.retry( + () => _delegate.sendByteStream( + method, + uri, + byteStream, + length, + headers, + ), + retryIf: (e) => e is SocketException || e is TimeoutException, + ); + } +} +``` + +### Example: Mock HTTP Service (Testing) + +```dart +class MockHttpService implements HttpService { + final Map _mockResponses = {}; + bool _initialized = false; + + void addMockResponse(String key, http.Response response) { + _mockResponses[key] = response; + } + + @override + Future initialize() async { + _initialized = true; + } + + @override + void terminate() { + _initialized = false; + _mockResponses.clear(); + } + + @override + Future ping(Uri uri, {required Duration timeout}) async { + return true; // Always return true in tests + } + + @override + Future send( + HttpMethod method, + Uri uri, { + Map? headers, + Map? params, + String? body, + }) async { + final key = '${method}_${uri.path}'; + if (_mockResponses.containsKey(key)) { + return _mockResponses[key]!; + } + return http.Response('{}', 200); + } + + @override + Future sendByteStream( + HttpMethod method, + Uri uri, + http.ByteStream byteStream, + int length, + Map headers, + ) async { + return http.Response('{"uploaded": true}', 200); + } +} +``` + +--- + +## Complete Examples + +### Basic Usage + +```dart +import 'package:clerk_auth/clerk_auth.dart'; + +void main() async { + final httpService = DefaultHttpService(); + await httpService.initialize(); + + final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: Persistor.none, + httpService: httpService, + ); + + final auth = Auth(config: config); + await auth.initialize(); + + // Use auth... + + auth.terminate(); + httpService.terminate(); +} +``` + +### With Logging + +```dart +import 'package:clerk_auth/clerk_auth.dart'; + +void main() async { + final baseService = DefaultHttpService(); + final loggingService = LoggingHttpService(baseService); + await loggingService.initialize(); + + final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: Persistor.none, + httpService: loggingService, + ); + + final auth = Auth(config: config); + await auth.initialize(); + + // All HTTP requests will be logged +} +``` + +### Testing Setup + +```dart +import 'package:clerk_auth/clerk_auth.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; + +void main() { + test('auth test with mock HTTP', () async { + final mockHttp = MockHttpService(); + mockHttp.addMockResponse( + 'POST_/v1/client', + http.Response('{"client": {}}', 200), + ); + + final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: Persistor.none, + httpService: mockHttp, + ); + + final auth = Auth(config: config); + await auth.initialize(); + + // Test with mocked responses + }); +} +``` + +--- + +## Best Practices + +1. **Use DefaultHttpService for most cases**: It's well-tested and handles common scenarios. + +2. **Implement custom services for specific needs**: + - Logging/debugging + - Custom retry logic + - Request/response transformation + - Testing with mocks + +3. **Handle initialization properly**: + - Always call `initialize()` before use + - Handle multiple initialization calls gracefully + - Clean up in `terminate()` + +4. **Manage client lifecycle**: + - Create clients in `initialize()` + - Close clients in `terminate()` + - Reuse clients when possible + +5. **Error handling**: + - Catch and handle network exceptions + - Implement retry logic for transient failures + - Log errors for debugging + +6. **Testing**: + - Use mock implementations in tests + - Test error scenarios + - Verify request/response handling + +7. **Performance**: + - Reuse HTTP clients + - Implement connection pooling + - Set appropriate timeouts + +--- + +## Troubleshooting + +### Connection Timeouts + +**Problem:** Requests timeout frequently. + +**Solutions:** +- Increase `httpConnectionTimeout` in `AuthConfig` +- Check network connectivity +- Verify API endpoint is reachable +- Implement retry logic + +### SSL/TLS Errors + +**Problem:** Certificate verification failures. + +**Solutions:** +- Ensure system certificates are up to date +- Check for man-in-the-middle proxies +- Verify API endpoint uses valid certificate +- Don't disable certificate validation in production + +### Memory Leaks + +**Problem:** HTTP clients not being cleaned up. + +**Solutions:** +- Always call `terminate()` when done +- Ensure clients are closed properly +- Use `DefaultHttpService` which manages lifecycle +- Profile memory usage + +### Request Failures + +**Problem:** Requests fail with various errors. + +**Solutions:** +- Check request parameters and headers +- Verify API endpoint and method +- Implement logging to debug requests +- Use retry logic for transient failures + +--- + +## Related Documentation + +- [AuthConfig Documentation](auth_config.md) +- [Auth Documentation](auth.md) +- [Persistor Documentation](persistor.md) + +--- + +## Additional Resources + +- [http package](https://pub.dev/packages/http) +- [retry package](https://pub.dev/packages/retry) +- [Clerk API Reference](https://clerk.com/docs/reference/backend-api) + +--- + +*Generated for clerk_auth version 0.0.14-beta* + + + diff --git a/docs/clerk_auth/persistor.md b/docs/clerk_auth/persistor.md new file mode 100644 index 00000000..0dc5a373 --- /dev/null +++ b/docs/clerk_auth/persistor.md @@ -0,0 +1,539 @@ +# Persistor Documentation + +This document provides comprehensive documentation for the `Persistor` abstract class and its implementations from `clerk_auth/lib/src/clerk_auth/persistor.dart`. + +## Overview + +The `Persistor` abstract class defines the persistence interface for storing authentication state between app sessions. It allows the Clerk SDK to maintain user sessions across app restarts. + +## Abstract Class + +```dart +abstract class Persistor { + Future initialize(); + void terminate(); + FutureOr read(String key); + FutureOr write(String key, T value); + FutureOr delete(String key); +} +``` + +--- + +## Methods + +### `initialize()` + +Initializes the persistor service. Called once when the Auth object is initialized. + +```dart +Future initialize() +``` + +**Example:** +```dart +final persistor = DefaultPersistor( + getCacheDirectory: getApplicationDocumentsDirectory, +); +await persistor.initialize(); +``` + +--- + +### `terminate()` + +Terminates the persistor service. Called when the Auth object is terminated. + +```dart +void terminate() +``` + +**Example:** +```dart +persistor.terminate(); +``` + +--- + +### `read()` + +Reads a value from storage by key. + +```dart +FutureOr read(String key) +``` + +**Parameters:** +- `key`: The storage key to read from + +**Returns:** +- The stored value of type `T`, or `null` if not found + +**Example:** +```dart +final clientData = await persistor.read>('client'); +final envData = await persistor.read>('env'); +``` + +--- + +### `write()` + +Writes a value to storage with the given key. + +```dart +FutureOr write(String key, T value) +``` + +**Parameters:** +- `key`: The storage key to write to +- `value`: The value to store + +**Example:** +```dart +await persistor.write('client', clientJson); +await persistor.write('env', envJson); +``` + +--- + +### `delete()` + +Deletes a value from storage by key. + +```dart +FutureOr delete(String key) +``` + +**Parameters:** +- `key`: The storage key to delete + +**Example:** +```dart +await persistor.delete('client'); +``` + +--- + +## Built-in Implementations + +### `Persistor.none` + +A no-op persistor used for testing. Does not persist any data. + +**Type:** `const Persistor` + +**Example:** +```dart +final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: Persistor.none, // No persistence +); +``` + +**Use cases:** +- Unit testing +- Integration testing +- Temporary sessions that shouldn't persist + +**Note:** Marked with `@visibleForTesting` - should only be used in test environments. + +--- + +### `DefaultPersistor` + +A default implementation that writes authentication state to the file system as JSON. + +**Constructor:** +```dart +DefaultPersistor({ + required DirectoryGetter getCacheDirectory, +}) +``` + +**Parameters:** +- `getCacheDirectory`: A function that returns the directory for file storage + +**Example:** +```dart +import 'package:path_provider/path_provider.dart'; + +final persistor = DefaultPersistor( + getCacheDirectory: getApplicationDocumentsDirectory, +); +``` + +**Behavior:** +- Stores all data in a single JSON file: `clerk_sdk.json` +- Writes are debounced by 600ms to avoid excessive I/O +- Automatically handles JSON encoding/decoding +- Recovers gracefully from corrupted JSON files + +**File location:** +- The file is stored in the directory returned by `getCacheDirectory` +- Full path: `{cacheDirectory}/clerk_sdk.json` + +--- + +## Custom Implementation + +You can create custom persistor implementations for specific storage backends. + +### Example: Shared Preferences Persistor + +```dart +import 'package:shared_preferences/shared_preferences.dart'; + +class SharedPreferencesPersistor implements Persistor { + SharedPreferences? _prefs; + + @override + Future initialize() async { + _prefs = await SharedPreferences.getInstance(); + } + + @override + void terminate() { + // SharedPreferences doesn't need cleanup + } + + @override + Future read(String key) async { + if (T == String) { + return _prefs?.getString(key) as T?; + } else if (T == int) { + return _prefs?.getInt(key) as T?; + } else if (T == bool) { + return _prefs?.getBool(key) as T?; + } else if (T == Map) { + final jsonString = _prefs?.getString(key); + if (jsonString != null) { + return json.decode(jsonString) as T; + } + } + return null; + } + + @override + Future write(String key, T value) async { + if (value is String) { + await _prefs?.setString(key, value); + } else if (value is int) { + await _prefs?.setInt(key, value); + } else if (value is bool) { + await _prefs?.setBool(key, value); + } else if (value is Map) { + await _prefs?.setString(key, json.encode(value)); + } + } + + @override + Future delete(String key) async { + await _prefs?.remove(key); + } +} +``` + +### Example: In-Memory Persistor (Testing) + +```dart +class InMemoryPersistor implements Persistor { + final _storage = {}; + + @override + Future initialize() async {} + + @override + void terminate() { + _storage.clear(); + } + + @override + FutureOr read(String key) { + return _storage[key] as T?; + } + + @override + FutureOr write(String key, T value) { + _storage[key] = value; + } + + @override + FutureOr delete(String key) { + _storage.remove(key); + } +} +``` + +--- + +## Storage Keys Used by Clerk + +The Clerk SDK uses the following keys for persistence: + +### `$client` + +Stores the current `Client` object containing: +- Sign-in state +- Sign-up state +- Active sessions +- User data + +**Type:** `Map` + +### `$env` + +Stores the `Environment` object containing: +- Clerk account configuration +- Enabled features +- Organization settings +- User attribute settings + +**Type:** `Map` + +**Note:** These keys are prefixed with `$` to avoid conflicts with user data. + +--- + +## Complete Examples + +### Basic Setup with DefaultPersistor + +```dart +import 'package:clerk_auth/clerk_auth.dart'; +import 'package:path_provider/path_provider.dart'; + +Future setupAuth() async { + final persistor = DefaultPersistor( + getCacheDirectory: getApplicationDocumentsDirectory, + ); + + final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: persistor, + ); + + final auth = Auth(config: config); + await auth.initialize(); +} +``` + +### Testing Setup with No Persistence + +```dart +import 'package:clerk_auth/clerk_auth.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('auth test', () async { + final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: Persistor.none, // No persistence in tests + ); + + final auth = Auth(config: config); + await auth.initialize(); + + // Test auth functionality + }); +} +``` + +### Custom Secure Storage Setup + +```dart +import 'package:clerk_auth/clerk_auth.dart'; + +Future setupSecureAuth() async { + final persistor = SecureStoragePersistor(); + + final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: persistor, + ); + + final auth = Auth(config: config); + await auth.initialize(); +} +``` + +### Platform-Specific Persistor + +```dart +import 'dart:io'; +import 'package:clerk_auth/clerk_auth.dart'; +import 'package:path_provider/path_provider.dart'; + +Future createPlatformPersistor() async { + if (Platform.isIOS || Platform.isAndroid) { + // Use secure storage on mobile + return SecureStoragePersistor(); + } else { + // Use file-based storage on desktop + return DefaultPersistor( + getCacheDirectory: getApplicationDocumentsDirectory, + ); + } +} + +Future setupAuth() async { + final persistor = await createPlatformPersistor(); + + final config = AuthConfig( + publishableKey: 'pk_test_...', + persistor: persistor, + ); + + final auth = Auth(config: config); + await auth.initialize(); +} +``` + +--- + +## Best Practices + +1. **Choose the right persistor for your use case**: + - `DefaultPersistor`: Good for most applications + - `SecureStoragePersistor`: For sensitive data on mobile + - `Persistor.none`: Only for testing + - Custom implementation: For specific requirements + +2. **Handle initialization properly**: + - Always call `initialize()` before use + - Handle initialization errors gracefully + - Don't assume initialization is instant + +3. **Clean up resources**: + - Call `terminate()` when done + - Clear sensitive data on logout + - Handle app lifecycle events + +4. **Error handling**: + - Implement proper error handling in custom persistors + - Recover gracefully from corrupted data + - Log errors for debugging + +5. **Performance considerations**: + - Debounce writes to avoid excessive I/O + - Use async operations for file/network storage + - Consider caching frequently accessed data + +6. **Security**: + - Use secure storage for sensitive tokens on mobile + - Encrypt data at rest if required + - Clear data on logout or app uninstall + +7. **Testing**: + - Use `Persistor.none` in unit tests + - Test custom persistors thoroughly + - Verify data persistence across app restarts + +--- + +## Troubleshooting + +### Data Not Persisting + +**Problem:** User sessions don't persist across app restarts. + +**Solutions:** +- Verify `initialize()` is called before use +- Check that `write()` is completing successfully +- Ensure storage directory exists and is writable +- Check for errors in persistor implementation + +### Corrupted Data + +**Problem:** App crashes or fails to load persisted data. + +**Solutions:** +- Implement error handling in `read()` method +- Clear corrupted data and start fresh +- Add data validation before persisting +- Use `DefaultPersistor` which handles corruption gracefully + +### Performance Issues + +**Problem:** App is slow due to excessive persistence operations. + +**Solutions:** +- Debounce write operations (like `DefaultPersistor` does) +- Use in-memory caching +- Batch multiple writes together +- Profile to identify bottlenecks + +### Platform-Specific Issues + +**Problem:** Persistor works on one platform but not another. + +**Solutions:** +- Use platform-specific storage APIs +- Test on all target platforms +- Handle platform differences in custom implementation +- Use conditional imports for platform-specific code + +--- + +## Related Documentation + +- [AuthConfig Documentation](auth_config.md) +- [Auth Documentation](auth.md) +- [HttpService Documentation](http_service.md) + +--- + +## Additional Resources + +- [path_provider package](https://pub.dev/packages/path_provider) +- [shared_preferences package](https://pub.dev/packages/shared_preferences) +- [flutter_secure_storage package](https://pub.dev/packages/flutter_secure_storage) + +--- + +*Generated for clerk_auth version 0.0.14-beta* + +### Example: Secure Storage Persistor + +```dart +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; + +class SecureStoragePersistor implements Persistor { + final _storage = FlutterSecureStorage(); + + @override + Future initialize() async { + // No initialization needed + } + + @override + void terminate() { + // No cleanup needed + } + + @override + Future read(String key) async { + final value = await _storage.read(key: key); + if (value == null) return null; + + if (T == String) { + return value as T; + } else { + return json.decode(value) as T; + } + } + + @override + Future write(String key, T value) async { + final stringValue = value is String ? value : json.encode(value); + await _storage.write(key: key, value: stringValue); + } + + @override + Future delete(String key) async { + await _storage.delete(key: key); + } +} +``` + + diff --git a/docs/clerk_flutter/README.md b/docs/clerk_flutter/README.md new file mode 100644 index 00000000..0728e545 --- /dev/null +++ b/docs/clerk_flutter/README.md @@ -0,0 +1,525 @@ +# Clerk Flutter Widgets Documentation + +Welcome to the comprehensive documentation for the `clerk_flutter` package widgets. This documentation covers all public widgets and provides detailed usage examples. + +## Documentation Index + +### Core Widgets + +1. **[ClerkAuth](clerk_auth.md)** - Root widget for Clerk authentication + - Initialization and configuration + - Access to authentication state + - Theme and localization support + +2. **[ClerkAuthBuilder](clerk_auth_builder.md)** - Conditional rendering based on auth state + - Build different UI for signed-in vs signed-out users + - Flexible builder pattern + +3. **[ClerkSignedIn](clerk_signed_in.md)** - Show content only when signed in + - Simple conditional widget + - Automatic state management + +4. **[ClerkSignedOut](clerk_signed_out.md)** - Show content only when signed out + - Complement to ClerkSignedIn + - Automatic state management + +5. **[ClerkErrorListener](clerk_error_listener.md)** - Handle authentication errors + - Automatic error display + - Custom error handling + +### Authentication Widgets + +6. **[ClerkAuthentication](clerk_authentication.md)** - Complete sign-in/sign-up UI + - Pre-built authentication flow + - Supports all authentication strategies + - Customizable appearance + +### User Widgets + +7. **[ClerkUserButton](clerk_user_button.md)** - User profile button + - Multi-session management + - User profile access + - Sign-out functionality + +### Organization Widgets + +8. **[ClerkOrganizationList](clerk_organization_list.md)** - Organization management + - List user's organizations + - Switch between organizations + - Create new organizations + +### Configuration & Theming + +9. **[ClerkAuthConfig](clerk_auth_config.md)** - Flutter-specific configuration + - Localization settings + - Deep linking support + - Custom loading widgets + +10. **[ClerkTheme](clerk_theme.md)** - Theming and styling + - Light and dark themes + - Custom colors and styles + - Theme customization + +--- + +## Quick Start + +### Basic Setup + +```dart +import 'package:clerk_flutter/clerk_flutter.dart'; +import 'package:flutter/material.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'My App', + builder: ClerkAuth.materialAppBuilder( + config: ClerkAuthConfig( + publishableKey: 'pk_test_...', + ), + ), + home: const HomePage(), + ); + } +} + +class HomePage extends StatelessWidget { + const HomePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Home')), + body: ClerkAuthBuilder( + signedInBuilder: (context, authState) { + return Center( + child: Text('Welcome, ${authState.user?.fullName}!'), + ); + }, + signedOutBuilder: (context, authState) { + return const Center( + child: ClerkAuthentication(), + ); + }, + ), + ); + } +} +``` + +--- + +## Key Concepts + +### Widget Hierarchy + +The typical widget hierarchy for a Clerk Flutter app: + +``` +MaterialApp + └─ ClerkAuth (via builder) + └─ ClerkErrorListener + └─ Your App Widgets + β”œβ”€ ClerkSignedIn + β”‚ └─ Protected Content + β”œβ”€ ClerkSignedOut + β”‚ └─ ClerkAuthentication + └─ ClerkAuthBuilder + β”œβ”€ signedInBuilder + └─ signedOutBuilder +``` + +### State Management + +All Clerk widgets automatically rebuild when authentication state changes. Access the current state using: + +```dart +// Get auth state with rebuild on change +final authState = ClerkAuth.of(context); + +// Get auth state without rebuild +final authState = ClerkAuth.of(context, listen: false); + +// Get just the user +final user = ClerkAuth.userOf(context); + +// Get just the session +final session = ClerkAuth.sessionOf(context); +``` + +### Conditional Rendering + +Three ways to conditionally render based on auth state: + +**1. ClerkSignedIn / ClerkSignedOut** +```dart +Column( + children: [ + ClerkSignedIn( + child: Text('You are signed in!'), + ), + ClerkSignedOut( + child: Text('Please sign in'), + ), + ], +) +``` + +**2. ClerkAuthBuilder** +```dart +ClerkAuthBuilder( + signedInBuilder: (context, authState) { + return Text('Hello, ${authState.user?.firstName}!'); + }, + signedOutBuilder: (context, authState) { + return const ClerkAuthentication(); + }, +) +``` + +**3. Manual Check** +```dart +final authState = ClerkAuth.of(context); +if (authState.isSignedIn) { + return Text('Signed in'); +} else { + return Text('Signed out'); +} +``` + +--- + +## Common Use Cases + +### Complete Authentication Flow + +```dart +class AuthPage extends StatelessWidget { + const AuthPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: ClerkAuthBuilder( + signedInBuilder: (context, authState) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Welcome, ${authState.user?.fullName}!'), + const SizedBox(height: 20), + const ClerkUserButton(), + ], + ); + }, + signedOutBuilder: (context, authState) { + return const ClerkAuthentication(); + }, + ), + ), + ); + } +} +``` + +### Protected Routes + +```dart +class ProtectedPage extends StatelessWidget { + const ProtectedPage({super.key}); + + @override + Widget build(BuildContext context) { + return ClerkSignedIn( + child: Scaffold( + appBar: AppBar( + title: const Text('Protected Page'), + actions: const [ClerkUserButton()], + ), + body: const Center( + child: Text('This content is only visible to signed-in users'), + ), + ), + ); + } +} +``` + +### Multi-Session Support + +```dart +class MultiSessionExample extends StatelessWidget { + const MultiSessionExample({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Multi-Session'), + actions: const [ + ClerkUserButton( + showName: true, + ), + ], + ), + body: const Center( + child: Text('Switch between accounts using the user button'), + ), + ); + } +} +``` + +### Organization Management + +```dart +class OrganizationPage extends StatelessWidget { + const OrganizationPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Organizations'), + ), + body: const ClerkOrganizationList(), + ); + } +} +``` + +### Custom Error Handling + +```dart +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + builder: (context, child) { + return ClerkAuth( + config: ClerkAuthConfig( + publishableKey: 'pk_test_...', + ), + child: ClerkErrorListener( + handler: (context, error) { + // Custom error handling + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Error'), + content: Text(error.message), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('OK'), + ), + ], + ), + ); + }, + child: child!, + ), + ); + }, + home: const HomePage(), + ); + } +} +``` + +--- + +## Theming + +### Using Built-in Themes + +```dart +MaterialApp( + theme: ThemeData.light().copyWith( + extensions: [ClerkThemeExtension.light], + ), + darkTheme: ThemeData.dark().copyWith( + extensions: [ClerkThemeExtension.dark], + ), + // ... rest of app +) +``` + +### Custom Theme + +```dart +final customTheme = ClerkThemeExtension( + colors: ClerkThemeColors( + background: Colors.white, + altBackground: Colors.grey[100]!, + borderSide: Colors.grey[300]!, + text: Colors.black87, + icon: Colors.grey[600]!, + lightweightText: Colors.grey[500]!, + error: Colors.red, + accent: Colors.blue, + ), +); + +MaterialApp( + theme: ThemeData.light().copyWith( + extensions: [customTheme], + ), + // ... rest of app +) +``` + +--- + +## Localization + +### Adding Custom Localizations + +```dart +ClerkAuthConfig( + publishableKey: 'pk_test_...', + localizations: { + 'en': ClerkSdkLocalizationsEn(), + 'es': MySpanishLocalizations(), + }, + fallbackLocalization: ClerkSdkLocalizationsEn(), +) +``` + +### Accessing Localizations + +```dart +final l10ns = ClerkAuth.localizationsOf(context); +Text(l10ns.signIn); +Text(l10ns.signUp); +``` + +--- + +## Deep Linking + +### Setup Deep Linking for OAuth + +```dart +import 'package:app_links/app_links.dart'; + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + final _appLinks = AppLinks(); + late final Stream _deepLinkStream; + + @override + void initState() { + super.initState(); + _deepLinkStream = _appLinks.uriLinkStream; + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + builder: (context, child) { + return ClerkAuth( + config: ClerkAuthConfig( + publishableKey: 'pk_test_...', + deepLinkStream: _deepLinkStream, + redirectionGenerator: (context, strategy) { + return Uri.parse('myapp://oauth-callback'); + }, + ), + child: ClerkErrorListener(child: child!), + ); + }, + home: const HomePage(), + ); + } +} +``` + +--- + +## Best Practices + +1. **Always wrap your app with ClerkAuth**: Use `ClerkAuth.materialAppBuilder()` or wrap manually +2. **Include ClerkErrorListener**: Place it below ClerkAuth to handle errors automatically +3. **Use conditional widgets**: Prefer `ClerkSignedIn`/`ClerkSignedOut` for simple cases +4. **Access state efficiently**: Use `listen: false` when you don't need rebuilds +5. **Customize themes**: Override `ClerkThemeExtension` to match your app's design +6. **Handle deep links**: Set up deep linking for OAuth flows +7. **Test with different states**: Test your UI in signed-in, signed-out, and loading states + +--- + +## Testing + +### Unit Testing + +```dart +import 'package:clerk_flutter/clerk_flutter.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('shows sign-in UI when signed out', (tester) async { + await tester.pumpWidget( + MaterialApp( + builder: (context, child) { + return ClerkAuth( + config: ClerkAuthConfig( + publishableKey: 'pk_test_...', + ), + child: child!, + ); + }, + home: const Scaffold( + body: ClerkAuthBuilder( + signedOutBuilder: (context, authState) { + return const Text('Sign In'); + }, + ), + ), + ), + ); + + await tester.pumpAndSettle(); + expect(find.text('Sign In'), findsOneWidget); + }); +} +``` + +--- + +## Additional Resources + +- [Clerk Dashboard](https://dashboard.clerk.com/) +- [Clerk API Reference](https://clerk.com/docs/reference/backend-api) +- [Flutter SDK Guide](https://clerk.com/docs/quickstarts/flutter) +- [clerk_flutter on pub.dev](https://pub.dev/packages/clerk_flutter) +- [clerk_auth Documentation](../clerk_auth/README.md) + +--- + +## Version + +*Documentation generated for clerk_flutter version 0.0.14-beta* + +--- + +## Contributing + +Found an issue or want to improve the documentation? Please open an issue or pull request on the [GitHub repository](https://github.com/clerk/clerk-sdk-flutter). + diff --git a/docs/clerk_flutter/clerk_auth.md b/docs/clerk_flutter/clerk_auth.md new file mode 100644 index 00000000..2b2db6e4 --- /dev/null +++ b/docs/clerk_flutter/clerk_auth.md @@ -0,0 +1,506 @@ +# ClerkAuth Widget Documentation + +The `ClerkAuth` widget is the root control widget that initializes the Clerk authentication system for your Flutter application. + +## Overview + +`ClerkAuth` must be placed at the root of your widget tree (or near it) to provide authentication state to all descendant widgets. It manages the `ClerkAuthState` object and makes it available throughout your app via `InheritedWidget`. + +## Class Definition + +```dart +class ClerkAuth extends StatefulWidget { + const ClerkAuth({ + super.key, + required this.child, + ClerkAuthConfig? config, + this.persistor, + this.httpService, + this.authState, + }); +} +``` + +--- + +## Constructors + +### Default Constructor + +```dart +const ClerkAuth({ + super.key, + required this.child, + ClerkAuthConfig? config, + this.persistor, + this.httpService, + this.authState, +}) +``` + +**Parameters:** +- `child`: The widget tree to wrap +- `config`: Configuration for Clerk authentication (required if `authState` is null) +- `persistor`: Optional override for the default persistor +- `httpService`: Optional override for the default HTTP service +- `authState`: Optional pre-created `ClerkAuthState` (required if `config` is null) + +**Note:** You must provide either `config` OR `authState`, but not both. + +--- + +### MaterialApp Builder + +```dart +static TransitionBuilder materialAppBuilder({ + required ClerkAuthConfig config, + Stream? deepLinkStream, +}) +``` + +Convenience method for use with `MaterialApp.builder`. + +**Parameters:** +- `config`: Configuration for Clerk authentication +- `deepLinkStream`: Optional stream of deep links for OAuth + +**Example:** +```dart +MaterialApp( + title: 'My App', + builder: ClerkAuth.materialAppBuilder( + config: ClerkAuthConfig( + publishableKey: 'pk_test_...', + ), + ), + home: const HomePage(), +) +``` + +--- + +## Static Methods + +### `of()` + +Get the nearest `ClerkAuthState` from the widget tree. + +```dart +static ClerkAuthState of(BuildContext context, {bool listen = true}) +``` + +**Parameters:** +- `context`: The build context +- `listen`: Whether to rebuild when auth state changes (default: `true`) + +**Returns:** `ClerkAuthState` + +**Example:** +```dart +// With rebuild on change +final authState = ClerkAuth.of(context); + +// Without rebuild +final authState = ClerkAuth.of(context, listen: false); +``` + +--- + +### `userOf()` + +Get the current user from the widget tree. + +```dart +static User? userOf(BuildContext context) +``` + +**Returns:** The current `User` or `null` if not signed in + +**Example:** +```dart +final user = ClerkAuth.userOf(context); +if (user != null) { + print('User: ${user.fullName}'); +} +``` + +--- + +### `sessionOf()` + +Get the current session from the widget tree. + +```dart +static Session? sessionOf(BuildContext context) +``` + +**Returns:** The current `Session` or `null` if not signed in + +**Example:** +```dart +final session = ClerkAuth.sessionOf(context); +if (session != null) { + print('Session ID: ${session.id}'); +} +``` + +--- + +### `localizationsOf()` + +Get the localizations for the current locale. + +```dart +static ClerkSdkLocalizations localizationsOf(BuildContext context) +``` + +**Returns:** `ClerkSdkLocalizations` for the current locale + +**Example:** +```dart +final l10ns = ClerkAuth.localizationsOf(context); +Text(l10ns.signIn); +Text(l10ns.signUp); +``` + +--- + +### `displayConfigOf()` + +Get the display configuration from the environment. + +```dart +static DisplayConfig displayConfigOf(BuildContext context) +``` + +**Returns:** `DisplayConfig` containing application name and branding + +**Example:** +```dart +final display = ClerkAuth.displayConfigOf(context); +Text('Welcome to ${display.applicationName}'); +``` + +--- + +### `errorStreamOf()` + +Get the stream of Clerk errors. + +```dart +static Stream errorStreamOf(BuildContext context) +``` + +**Returns:** Stream of `ClerkError` objects + +**Example:** +```dart +ClerkAuth.errorStreamOf(context).listen((error) { + print('Error: ${error.message}'); +}); +``` + +--- + +### `fileCacheOf()` + +Get the file cache from the configuration. + +```dart +static ClerkFileCache fileCacheOf(BuildContext context) +``` + +**Returns:** `ClerkFileCache` for caching remote files + +--- + +### `themeExtensionOf()` + +Get the Clerk theme extension from the current theme. + +```dart +static ClerkThemeExtension themeExtensionOf(BuildContext context) +``` + +**Returns:** `ClerkThemeExtension` with colors and styles + +**Example:** +```dart +final theme = ClerkAuth.themeExtensionOf(context); +Container( + color: theme.colors.background, + child: Text( + 'Hello', + style: theme.styles.heading, + ), +) +``` + +--- + +## Complete Examples + +### Basic Setup + +```dart +import 'package:clerk_flutter/clerk_flutter.dart'; +import 'package:flutter/material.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'My App', + builder: ClerkAuth.materialAppBuilder( + config: ClerkAuthConfig( + publishableKey: 'pk_test_...', + ), + ), + home: const HomePage(), + ); + } +} +``` + +### Manual Setup + +```dart +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + builder: (context, child) { + return ClerkAuth( + config: ClerkAuthConfig( + publishableKey: 'pk_test_...', + ), + child: ClerkErrorListener(child: child!), + ); + }, + home: const HomePage(), + ); + } +} +``` + +### With Custom Persistor + +```dart +import 'package:clerk_auth/clerk_auth.dart' as clerk; + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + builder: (context, child) { + return ClerkAuth( + config: ClerkAuthConfig( + publishableKey: 'pk_test_...', + ), + persistor: clerk.Persistor.none, // No persistence + child: ClerkErrorListener(child: child!), + ); + }, + home: const HomePage(), + ); + } +} +``` + +### Accessing Auth State + +```dart +class HomePage extends StatelessWidget { + const HomePage({super.key}); + + @override + Widget build(BuildContext context) { + // Get auth state with rebuild + final authState = ClerkAuth.of(context); + + // Get user directly + final user = ClerkAuth.userOf(context); + + // Get session directly + final session = ClerkAuth.sessionOf(context); + + return Scaffold( + appBar: AppBar( + title: Text(user != null ? 'Welcome ${user.firstName}' : 'Welcome'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (authState.isSignedIn) + Text('Signed in as ${user?.emailAddress}') + else + const Text('Not signed in'), + const SizedBox(height: 20), + if (session != null) + Text('Session expires: ${session.expireAt}'), + ], + ), + ), + ); + } +} +``` + +### Using Localizations + +```dart +class LocalizedWidget extends StatelessWidget { + const LocalizedWidget({super.key}); + + @override + Widget build(BuildContext context) { + final l10ns = ClerkAuth.localizationsOf(context); + + return Column( + children: [ + Text(l10ns.signIn), + Text(l10ns.signUp), + Text(l10ns.emailAddress), + Text(l10ns.password), + ], + ); + } +} +``` + +### Using Theme + +```dart +class ThemedWidget extends StatelessWidget { + const ThemedWidget({super.key}); + + @override + Widget build(BuildContext context) { + final theme = ClerkAuth.themeExtensionOf(context); + + return Container( + decoration: BoxDecoration( + color: theme.colors.background, + border: Border.all(color: theme.colors.borderSide), + ), + padding: const EdgeInsets.all(16), + child: Column( + children: [ + Text( + 'Heading', + style: theme.styles.heading, + ), + Text( + 'Subheading', + style: theme.styles.subheading, + ), + Text( + 'Body text', + style: theme.styles.text, + ), + ], + ), + ); + } +} +``` + +--- + +## Best Practices + +1. **Place ClerkAuth at the root**: Wrap your entire app or use `materialAppBuilder()` +2. **Include ClerkErrorListener**: Always include error handling below ClerkAuth +3. **Use listen: false when appropriate**: Avoid unnecessary rebuilds by setting `listen: false` when you don't need updates +4. **Access state efficiently**: Use specific getters like `userOf()` when you only need one piece of data +5. **Handle loading state**: The config's `loading` widget is shown while initializing +6. **Don't create multiple instances**: Only create one ClerkAuth widget per app + +--- + +## Common Patterns + +### Conditional Rendering Based on Auth State + +```dart +class ConditionalContent extends StatelessWidget { + const ConditionalContent({super.key}); + + @override + Widget build(BuildContext context) { + final authState = ClerkAuth.of(context); + + if (authState.isNotAvailable) { + return const CircularProgressIndicator(); + } + + if (authState.isSignedIn) { + return const SignedInContent(); + } + + return const SignedOutContent(); + } +} +``` + +### Listening to Errors + +```dart +class ErrorAwareWidget extends StatefulWidget { + const ErrorAwareWidget({super.key}); + + @override + State createState() => _ErrorAwareWidgetState(); +} + +class _ErrorAwareWidgetState extends State { + StreamSubscription? _errorSub; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _errorSub?.cancel(); + _errorSub = ClerkAuth.errorStreamOf(context).listen((error) { + // Handle error + print('Error: ${error.message}'); + }); + } + + @override + void dispose() { + _errorSub?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} +``` + +--- + +## Related Documentation + +- [ClerkAuthConfig](clerk_auth_config.md) +- [ClerkAuthBuilder](clerk_auth_builder.md) +- [ClerkErrorListener](clerk_error_listener.md) +- [ClerkTheme](clerk_theme.md) +- [Auth (clerk_auth)](../clerk_auth/auth.md) + +--- + +*Generated for clerk_flutter version 0.0.14-beta* + diff --git a/docs/clerk_flutter/clerk_auth_builder.md b/docs/clerk_flutter/clerk_auth_builder.md new file mode 100644 index 00000000..a567d6aa --- /dev/null +++ b/docs/clerk_flutter/clerk_auth_builder.md @@ -0,0 +1,327 @@ +# ClerkAuthBuilder Widget Documentation + +The `ClerkAuthBuilder` widget provides a flexible builder pattern for rendering different UI based on authentication state. + +## Overview + +`ClerkAuthBuilder` allows you to specify different builders for signed-in and signed-out states, giving you fine-grained control over your UI. It automatically rebuilds when authentication state changes. + +## Class Definition + +```dart +class ClerkAuthBuilder extends StatefulWidget { + const ClerkAuthBuilder({ + super.key, + this.signedInBuilder, + this.signedOutBuilder, + this.builder, + }); +} +``` + +--- + +## Parameters + +### `signedInBuilder` + +**Type:** `AuthWidgetBuilder?` (i.e., `Widget Function(BuildContext, ClerkAuthState)`) + +Builder invoked when a user is signed in (i.e., when `authState.user` is not null). + +**Example:** +```dart +ClerkAuthBuilder( + signedInBuilder: (context, authState) { + return Text('Welcome, ${authState.user?.fullName}!'); + }, +) +``` + +--- + +### `signedOutBuilder` + +**Type:** `AuthWidgetBuilder?` + +Builder invoked when no user is signed in (i.e., when `authState.user` is null). + +**Example:** +```dart +ClerkAuthBuilder( + signedOutBuilder: (context, authState) { + return const Text('Please sign in'); + }, +) +``` + +--- + +### `builder` + +**Type:** `AuthWidgetBuilder?` + +Fallback builder invoked when neither `signedInBuilder` nor `signedOutBuilder` is provided, or when they don't match the current state. + +**Example:** +```dart +ClerkAuthBuilder( + builder: (context, authState) { + return Text('Auth state: ${authState.isSignedIn}'); + }, +) +``` + +--- + +## Behavior + +The widget chooses which builder to invoke based on the following logic: + +1. If `signedInBuilder` is provided AND user is signed in β†’ use `signedInBuilder` +2. Else if `signedOutBuilder` is provided AND user is signed out β†’ use `signedOutBuilder` +3. Else if `builder` is provided β†’ use `builder` +4. Else β†’ return empty widget + +--- + +## Complete Examples + +### Basic Sign-In/Sign-Out UI + +```dart +class HomePage extends StatelessWidget { + const HomePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: ClerkAuthBuilder( + signedInBuilder: (context, authState) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Welcome, ${authState.user?.fullName}!'), + const SizedBox(height: 20), + ElevatedButton( + onPressed: () => authState.signOut(), + child: const Text('Sign Out'), + ), + ], + ), + ); + }, + signedOutBuilder: (context, authState) { + return const Center( + child: ClerkAuthentication(), + ); + }, + ), + ); + } +} +``` + +### With Loading State + +```dart +class LoadingAwareBuilder extends StatelessWidget { + const LoadingAwareBuilder({super.key}); + + @override + Widget build(BuildContext context) { + return ClerkAuthBuilder( + builder: (context, authState) { + if (authState.isNotAvailable) { + return const Center( + child: CircularProgressIndicator(), + ); + } + + if (authState.isSignedIn) { + return Text('Signed in as ${authState.user?.emailAddress}'); + } + + return const Text('Not signed in'); + }, + ); + } +} +``` + +### Accessing User Data + +```dart +class UserProfile extends StatelessWidget { + const UserProfile({super.key}); + + @override + Widget build(BuildContext context) { + return ClerkAuthBuilder( + signedInBuilder: (context, authState) { + final user = authState.user!; + + return Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (user.imageUrl != null) + CircleAvatar( + backgroundImage: NetworkImage(user.imageUrl!), + radius: 40, + ), + const SizedBox(height: 16), + Text('Name: ${user.fullName}'), + Text('Email: ${user.emailAddress}'), + Text('Username: ${user.username ?? "N/A"}'), + Text('Created: ${user.createdAt}'), + ], + ), + ), + ); + }, + signedOutBuilder: (context, authState) { + return const Card( + child: Padding( + padding: EdgeInsets.all(16), + child: Text('No user profile available'), + ), + ); + }, + ); + } +} +``` + +### Nested Builders + +```dart +class NestedExample extends StatelessWidget { + const NestedExample({super.key}); + + @override + Widget build(BuildContext context) { + return ClerkAuthBuilder( + signedInBuilder: (context, authState) { + return Column( + children: [ + Text('User: ${authState.user?.fullName}'), + // Nested builder for organization state + if (authState.organization != null) + Text('Org: ${authState.organization?.name}') + else + const Text('No active organization'), + ], + ); + }, + signedOutBuilder: (context, authState) { + return const ClerkAuthentication(); + }, + ); + } +} +``` + +### Conditional Navigation + +```dart +class ConditionalNavigation extends StatelessWidget { + const ConditionalNavigation({super.key}); + + @override + Widget build(BuildContext context) { + return ClerkAuthBuilder( + signedInBuilder: (context, authState) { + // Navigate to home when signed in + WidgetsBinding.instance.addPostFrameCallback((_) { + Navigator.of(context).pushReplacementNamed('/home'); + }); + return const SizedBox.shrink(); + }, + signedOutBuilder: (context, authState) { + // Navigate to login when signed out + WidgetsBinding.instance.addPostFrameCallback((_) { + Navigator.of(context).pushReplacementNamed('/login'); + }); + return const SizedBox.shrink(); + }, + ); + } +} +``` + +--- + +## Best Practices + +1. **Use specific builders**: Prefer `signedInBuilder` and `signedOutBuilder` over the generic `builder` for clarity +2. **Handle all states**: Consider what happens during loading, signing in, and signing up +3. **Avoid side effects in builders**: Don't perform navigation or state changes directly in builders (use `addPostFrameCallback` if needed) +4. **Keep builders pure**: Builders should only return widgets based on the current state +5. **Access authState parameter**: Use the provided `authState` parameter instead of calling `ClerkAuth.of(context)` again + +--- + +## Common Patterns + +### Dashboard with Sidebar + +```dart +class Dashboard extends StatelessWidget { + const Dashboard({super.key}); + + @override + Widget build(BuildContext context) { + return ClerkAuthBuilder( + signedInBuilder: (context, authState) { + return Row( + children: [ + // Sidebar + NavigationRail( + destinations: const [ + NavigationRailDestination( + icon: Icon(Icons.home), + label: Text('Home'), + ), + NavigationRailDestination( + icon: Icon(Icons.settings), + label: Text('Settings'), + ), + ], + selectedIndex: 0, + ), + // Main content + Expanded( + child: Center( + child: Text('Welcome, ${authState.user?.firstName}!'), + ), + ), + ], + ); + }, + signedOutBuilder: (context, authState) { + return const Center( + child: ClerkAuthentication(), + ); + }, + ); + } +} +``` + +--- + +## Related Documentation + +- [ClerkAuth](clerk_auth.md) +- [ClerkSignedIn](clerk_signed_in.md) +- [ClerkSignedOut](clerk_signed_out.md) +- [ClerkAuthentication](clerk_authentication.md) + +--- + +*Generated for clerk_flutter version 0.0.14-beta* + diff --git a/docs/clerk_flutter/clerk_auth_config.md b/docs/clerk_flutter/clerk_auth_config.md new file mode 100644 index 00000000..b3ea3931 --- /dev/null +++ b/docs/clerk_flutter/clerk_auth_config.md @@ -0,0 +1,229 @@ +# ClerkAuthConfig Documentation + +The `ClerkAuthConfig` class configures the Clerk authentication system for Flutter applications. + +## Overview + +`ClerkAuthConfig` is a Flutter-specific wrapper around the core `clerk_auth.AuthConfig` that adds Flutter-specific configuration options like loading widgets, file caching, and localizations. + +## Class Definition + +```dart +class ClerkAuthConfig { + ClerkAuthConfig({ + required this.publishableKey, + this.loading, + this.fileCache, + this.localizations, + this.telemetry = true, + this.sdkFlags, + }); +} +``` + +--- + +## Parameters + +### `publishableKey` + +**Type:** `String` (required) + +Your Clerk publishable key from the Clerk Dashboard. + +**Example:** +```dart +ClerkAuthConfig( + publishableKey: 'pk_test_...', +) +``` + +--- + +### `loading` + +**Type:** `Widget?` + +Widget to display while Clerk is initializing. If null, shows a default loading indicator. + +**Example:** +```dart +ClerkAuthConfig( + publishableKey: 'pk_test_...', + loading: const Center( + child: CircularProgressIndicator(), + ), +) +``` + +--- + +### `fileCache` + +**Type:** `ClerkFileCache?` + +Custom file cache for storing remote files (avatars, logos, etc.). If null, uses default cache. + +**Example:** +```dart +ClerkAuthConfig( + publishableKey: 'pk_test_...', + fileCache: MyCustomFileCache(), +) +``` + +--- + +### `localizations` + +**Type:** `Map?` + +Custom localizations for different locales. If null, uses default English localizations. + +**Example:** +```dart +ClerkAuthConfig( + publishableKey: 'pk_test_...', + localizations: { + 'en': ClerkSdkLocalizationsEn(), + 'es': MySpanishLocalizations(), + 'fr': MyFrenchLocalizations(), + }, +) +``` + +--- + +### `telemetry` + +**Type:** `bool` +**Default:** `true` + +Whether to enable telemetry. Set to `false` to disable usage tracking. + +**Example:** +```dart +ClerkAuthConfig( + publishableKey: 'pk_test_...', + telemetry: false, // Disable telemetry +) +``` + +--- + +### `sdkFlags` + +**Type:** `SdkFlags?` + +Advanced SDK flags for experimental features. Generally not needed. + +--- + +## Complete Examples + +### Basic Configuration + +```dart +MaterialApp( + builder: ClerkAuth.materialAppBuilder( + config: ClerkAuthConfig( + publishableKey: 'pk_test_...', + ), + ), + home: const HomePage(), +) +``` + +### With Custom Loading Widget + +```dart +ClerkAuthConfig( + publishableKey: 'pk_test_...', + loading: Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset('assets/logo.png', width: 100), + const SizedBox(height: 20), + const CircularProgressIndicator(), + const SizedBox(height: 20), + const Text('Loading...'), + ], + ), + ), + ), +) +``` + +### With Localizations + +```dart +ClerkAuthConfig( + publishableKey: 'pk_test_...', + localizations: { + 'en': ClerkSdkLocalizationsEn(), + 'es': ClerkSdkLocalizationsEs(), + }, +) +``` + +### Production Configuration + +```dart +ClerkAuthConfig( + publishableKey: const String.fromEnvironment('CLERK_PUBLISHABLE_KEY'), + telemetry: true, + loading: const SplashScreen(), +) +``` + +### Development Configuration + +```dart +ClerkAuthConfig( + publishableKey: 'pk_test_...', + telemetry: false, // Disable in development + loading: const Center( + child: Text('Loading Clerk...'), + ), +) +``` + +--- + +## Environment Variables + +It's recommended to use environment variables for your publishable key: + +```dart +// Run with: flutter run --dart-define=CLERK_PUBLISHABLE_KEY=pk_test_... +ClerkAuthConfig( + publishableKey: const String.fromEnvironment( + 'CLERK_PUBLISHABLE_KEY', + defaultValue: 'pk_test_fallback', + ), +) +``` + +--- + +## Best Practices + +1. **Use environment variables**: Don't hardcode keys in source code +2. **Customize loading**: Provide a branded loading experience +3. **Enable telemetry in production**: Helps Clerk improve the SDK +4. **Disable telemetry in development**: Avoid polluting analytics +5. **Provide localizations**: Support your users' languages + +--- + +## Related Documentation + +- [ClerkAuth](clerk_auth.md) +- [AuthConfig (clerk_auth)](../clerk_auth/auth_config.md) + +--- + +*Generated for clerk_flutter version 0.0.14-beta* + diff --git a/docs/clerk_flutter/clerk_authentication.md b/docs/clerk_flutter/clerk_authentication.md new file mode 100644 index 00000000..56b6ff4f --- /dev/null +++ b/docs/clerk_flutter/clerk_authentication.md @@ -0,0 +1,318 @@ +# ClerkAuthentication Widget Documentation + +The `ClerkAuthentication` widget renders a complete, pre-built UI for signing users in or up. + +## Overview + +`ClerkAuthentication` provides a full-featured authentication interface that includes: +- Sign-in and sign-up forms +- OAuth provider buttons +- Email/password authentication +- Phone number authentication +- Email/SMS verification codes +- Password reset +- Automatic switching between sign-in and sign-up modes + +The functionality is controlled by your Clerk Dashboard settings (enabled strategies, required fields, etc.). + +## Class Definition + +```dart +class ClerkAuthentication extends StatefulWidget { + const ClerkAuthentication({super.key}); +} +``` + +--- + +## Features + +- **Automatic mode switching**: Switches between sign-in and sign-up based on user actions +- **OAuth integration**: Displays configured OAuth providers (Google, Apple, GitHub, etc.) +- **Multi-factor authentication**: Supports 2FA when enabled +- **Responsive design**: Adapts to different screen sizes +- **Themed**: Respects `ClerkThemeExtension` from your app theme +- **Localized**: Uses `ClerkSdkLocalizations` for translations + +--- + +## Complete Examples + +### Basic Usage + +```dart +class LoginPage extends StatelessWidget { + const LoginPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: ClerkSignedOut( + child: const ClerkAuthentication(), + ), + ), + ); + } +} +``` + +### Full-Screen Authentication + +```dart +class AuthPage extends StatelessWidget { + const AuthPage({super.key}); + + @override + Widget build(BuildContext context) { + return const Scaffold( + body: SafeArea( + child: Center( + child: SingleChildScrollView( + child: ClerkAuthentication(), + ), + ), + ), + ); + } +} +``` + +### With Background + +```dart +class StyledAuthPage extends StatelessWidget { + const StyledAuthPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Colors.blue.shade400, Colors.purple.shade400], + ), + ), + child: const Center( + child: Card( + margin: EdgeInsets.all(32), + child: Padding( + padding: EdgeInsets.all(16), + child: ClerkAuthentication(), + ), + ), + ), + ), + ); + } +} +``` + +### Conditional Rendering + +```dart +class HomePage extends StatelessWidget { + const HomePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Home')), + body: ClerkAuthBuilder( + signedInBuilder: (context, authState) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Welcome, ${authState.user?.fullName}!'), + const SizedBox(height: 20), + const ClerkUserButton(), + ], + ), + ); + }, + signedOutBuilder: (context, authState) { + return const Center( + child: ClerkAuthentication(), + ); + }, + ), + ); + } +} +``` + +### In a Dialog + +```dart +void showAuthDialog(BuildContext context) { + showDialog( + context: context, + builder: (context) => Dialog( + child: Container( + constraints: const BoxConstraints(maxWidth: 400), + padding: const EdgeInsets.all(16), + child: const ClerkAuthentication(), + ), + ), + ); +} +``` + +--- + +## Supported Authentication Strategies + +The widget automatically displays UI for strategies enabled in your Clerk Dashboard: + +**First-factor strategies:** +- Email + Password +- Email + Verification Code +- Phone + Verification Code +- Username + Password +- OAuth providers (Google, Apple, Facebook, GitHub, etc.) +- Passkeys (when supported) + +**Second-factor strategies:** +- SMS Code +- TOTP (Authenticator app) +- Backup codes + +--- + +## Customization + +### Theme Customization + +```dart +MaterialApp( + theme: ThemeData.light().copyWith( + extensions: [ + ClerkThemeExtension( + colors: ClerkThemeColors( + background: Colors.white, + altBackground: Colors.grey[100]!, + borderSide: Colors.grey[300]!, + text: Colors.black87, + icon: Colors.grey[600]!, + lightweightText: Colors.grey[500]!, + error: Colors.red, + accent: Colors.blue, // Primary button color + ), + ), + ], + ), + // ... rest of app +) +``` + +### Localization + +The widget uses your configured localizations: + +```dart +ClerkAuthConfig( + publishableKey: 'pk_test_...', + localizations: { + 'en': ClerkSdkLocalizationsEn(), + 'es': MySpanishLocalizations(), + }, +) +``` + +--- + +## Behavior + +### Sign-In Flow + +1. User enters identifier (email/phone/username) +2. User enters password or requests verification code +3. If verification code: user enters code +4. If 2FA enabled: user completes second factor +5. User is signed in + +### Sign-Up Flow + +1. User enters required fields (email, password, name, etc.) +2. User verifies email/phone with code +3. User is signed up and signed in + +### Mode Switching + +- "Don't have an account? Sign up" link switches to sign-up +- "Already have an account? Sign in" link switches to sign-in +- Automatic switching based on auth state + +--- + +## Best Practices + +1. **Use in signed-out state**: Wrap with `ClerkSignedOut` or use in `signedOutBuilder` +2. **Provide enough space**: The widget needs vertical space; use `SingleChildScrollView` if needed +3. **Configure in Dashboard**: Enable/disable strategies in your Clerk Dashboard +4. **Test all flows**: Test sign-in, sign-up, password reset, and 2FA +5. **Handle errors**: Use `ClerkErrorListener` to display authentication errors + +--- + +## Common Patterns + +### Modal Authentication + +```dart +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: ClerkAuthBuilder( + signedOutBuilder: (context, authState) { + return Center( + child: ElevatedButton( + onPressed: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) => DraggableScrollableSheet( + initialChildSize: 0.9, + builder: (context, scrollController) { + return const ClerkAuthentication(); + }, + ), + ); + }, + child: const Text('Sign In'), + ), + ); + }, + signedInBuilder: (context, authState) { + return Center( + child: Text('Welcome, ${authState.user?.fullName}!'), + ); + }, + ), + ), + ); + } +} +``` + +--- + +## Related Documentation + +- [ClerkAuth](clerk_auth.md) +- [ClerkAuthBuilder](clerk_auth_builder.md) +- [ClerkSignedOut](clerk_signed_out.md) +- [ClerkTheme](clerk_theme.md) +- [Auth (clerk_auth)](../clerk_auth/auth.md) + +--- + +*Generated for clerk_flutter version 0.0.14-beta* + diff --git a/docs/clerk_flutter/clerk_error_listener.md b/docs/clerk_flutter/clerk_error_listener.md new file mode 100644 index 00000000..652ebdc3 --- /dev/null +++ b/docs/clerk_flutter/clerk_error_listener.md @@ -0,0 +1,277 @@ +# ClerkErrorListener Widget Documentation + +The `ClerkErrorListener` widget listens to authentication errors and displays them to the user. + +## Overview + +`ClerkErrorListener` subscribes to the error stream from `ClerkAuthState` and automatically displays errors as SnackBars. You can also provide a custom error handler. + +## Class Definition + +```dart +class ClerkErrorListener extends StatefulWidget { + const ClerkErrorListener({ + super.key, + this.handler, + required this.child, + }); +} +``` + +--- + +## Parameters + +### `handler` + +**Type:** `ClerkErrorHandler?` (i.e., `FutureOr Function(BuildContext, ClerkError)`) + +Optional custom error handler. If not provided, errors are displayed as SnackBars. + +**Example:** +```dart +ClerkErrorListener( + handler: (context, error) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Error'), + content: Text(error.message), + ), + ); + }, + child: myApp, +) +``` + +--- + +### `child` + +**Type:** `Widget` (required) + +The widget tree to wrap. + +--- + +## Behavior + +**Default behavior (no handler):** +- Listens to `ClerkAuth.errorStreamOf(context)` +- Displays errors as SnackBars using `ScaffoldMessenger` +- Requires a `Scaffold` or `ScaffoldMessenger` ancestor in the widget tree + +**With custom handler:** +- Calls your handler function for each error +- You have full control over error display + +--- + +## Complete Examples + +### Basic Usage (Default Handler) + +```dart +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + builder: ClerkAuth.materialAppBuilder( + config: ClerkAuthConfig( + publishableKey: 'pk_test_...', + ), + ), + home: const HomePage(), + ); + } +} + +// Note: ClerkAuth.materialAppBuilder automatically includes ClerkErrorListener +``` + +### Manual Setup + +```dart +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + builder: (context, child) { + return ClerkAuth( + config: ClerkAuthConfig( + publishableKey: 'pk_test_...', + ), + child: ClerkErrorListener(child: child!), + ); + }, + home: const HomePage(), + ); + } +} +``` + +### Custom Error Handler (Dialog) + +```dart +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + builder: (context, child) { + return ClerkAuth( + config: ClerkAuthConfig( + publishableKey: 'pk_test_...', + ), + child: ClerkErrorListener( + handler: (context, error) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Authentication Error'), + content: Text(error.message), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('OK'), + ), + ], + ), + ); + }, + child: child!, + ), + ); + }, + home: const HomePage(), + ); + } +} +``` + +### Custom Error Handler (Toast) + +```dart +import 'package:fluttertoast/fluttertoast.dart'; + +ClerkErrorListener( + handler: (context, error) { + Fluttertoast.showToast( + msg: error.message, + toastLength: Toast.LENGTH_LONG, + gravity: ToastGravity.BOTTOM, + backgroundColor: Colors.red, + textColor: Colors.white, + ); + }, + child: myApp, +) +``` + +### Logging Errors + +```dart +import 'dart:developer' as developer; + +ClerkErrorListener( + handler: (context, error) async { + // Log to console + developer.log( + 'Clerk Error: ${error.message}', + name: 'clerk_flutter', + error: error, + ); + + // Also show to user + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(error.message)), + ); + }, + child: myApp, +) +``` + +### Error Analytics + +```dart +import 'package:firebase_crashlytics/firebase_crashlytics.dart'; + +ClerkErrorListener( + handler: (context, error) async { + // Report to Crashlytics + await FirebaseCrashlytics.instance.recordError( + error, + StackTrace.current, + reason: 'Clerk authentication error', + ); + + // Show to user + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(error.message)), + ); + }, + child: myApp, +) +``` + +--- + +## Error Types + +Errors are instances of `ClerkError` with the following properties: + +- `message`: Human-readable error message +- `code`: Error code (e.g., `'form_password_incorrect'`) +- `localizedMessage(localizations)`: Get localized error message + +--- + +## Best Practices + +1. **Place below ClerkAuth**: Always place `ClerkErrorListener` as a child of `ClerkAuth` +2. **Ensure Scaffold exists**: Default handler requires a `Scaffold` ancestor +3. **Use custom handler for special cases**: Implement custom handler for dialogs, logging, or analytics +4. **Don't throw in handler**: Handle errors gracefully in your custom handler +5. **Consider user experience**: Show clear, actionable error messages + +--- + +## Common Patterns + +### Conditional Error Display + +```dart +ClerkErrorListener( + handler: (context, error) { + // Only show certain errors to users + if (error.code == 'network_error') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Network error. Please check your connection.'), + ), + ); + } else { + // Log other errors silently + print('Clerk error: ${error.message}'); + } + }, + child: myApp, +) +``` + +--- + +## Related Documentation + +- [ClerkAuth](clerk_auth.md) +- [ClerkError (clerk_auth)](../clerk_auth/auth.md#error-handling) + +--- + +*Generated for clerk_flutter version 0.0.14-beta* + diff --git a/docs/clerk_flutter/clerk_organization_list.md b/docs/clerk_flutter/clerk_organization_list.md new file mode 100644 index 00000000..5f13706d --- /dev/null +++ b/docs/clerk_flutter/clerk_organization_list.md @@ -0,0 +1,303 @@ +# ClerkOrganizationList Widget Documentation + +The `ClerkOrganizationList` widget renders a list of the user's organizations with management capabilities. + +## Overview + +`ClerkOrganizationList` displays: +- All organizations the user belongs to +- Organization invitations +- Ability to switch active organization +- Ability to create new organizations +- Ability to manage organization settings + +## Class Definition + +```dart +class ClerkOrganizationList extends StatefulWidget { + const ClerkOrganizationList({ + super.key, + this.actions, + }); +} +``` + +--- + +## Parameters + +### `actions` + +**Type:** `List?` + +Custom actions to display in the organization list. If null, uses default actions (Create Organization). + +**Example:** +```dart +ClerkOrganizationList( + actions: [ + ClerkUserAction( + icon: Icons.add, + label: 'Create Organization', + callback: (context, authState) async { + // Custom create logic + }, + ), + ], +) +``` + +--- + +## Complete Examples + +### Basic Usage + +```dart +class OrganizationsPage extends StatelessWidget { + const OrganizationsPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Organizations'), + ), + body: const ClerkOrganizationList(), + ); + } +} +``` + +### In a Dialog + +```dart +void showOrganizationList(BuildContext context) { + showDialog( + context: context, + builder: (context) => Dialog( + child: Container( + constraints: const BoxConstraints(maxWidth: 500, maxHeight: 600), + child: const ClerkOrganizationList(), + ), + ), + ); +} +``` + +### With Custom Actions + +```dart +ClerkOrganizationList( + actions: [ + ClerkUserAction( + icon: Icons.add, + label: 'Create Organization', + callback: (context, authState) async { + // Show custom create dialog + showDialog( + context: context, + builder: (context) => const CreateOrgDialog(), + ); + }, + ), + ClerkUserAction( + icon: Icons.help, + label: 'Help', + callback: (context, authState) async { + // Show help + }, + ), + ], +) +``` + +### Conditional Display + +```dart +class HomePage extends StatelessWidget { + const HomePage({super.key}); + + @override + Widget build(BuildContext context) { + final authState = ClerkAuth.of(context); + + return Scaffold( + appBar: AppBar( + title: const Text('Home'), + actions: [ + if (authState.env.organization.isEnabled) + IconButton( + icon: const Icon(Icons.business), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const Scaffold( + body: ClerkOrganizationList(), + ), + ), + ); + }, + ), + ], + ), + body: const Center(child: Text('Content')), + ); + } +} +``` + +--- + +## Features + +### Organization List + +- Displays all organizations the user is a member of +- Shows current active organization +- Allows switching between organizations +- Shows organization role (admin, member, etc.) + +### Invitations + +- Displays pending organization invitations +- Allows accepting invitations +- Shows invitation details (organization name, role) + +### Organization Creation + +- Create new organizations (if enabled) +- Set organization name and slug +- Upload organization logo + +### Organization Management + +- Access organization settings +- Manage members +- Manage domains +- Leave organization + +--- + +## Default Actions + +1. **Create Organization** - Opens create organization panel (if user has permission) + +--- + +## Best Practices + +1. **Check if enabled**: Verify organizations are enabled before showing +2. **Handle permissions**: Check if user can create organizations +3. **Provide navigation**: Make it easy to access from your app +4. **Test invitations**: Test accepting and declining invitations +5. **Consider mobile**: The widget adapts to mobile screens + +--- + +## Common Patterns + +### In Navigation Drawer + +```dart +class MyDrawer extends StatelessWidget { + const MyDrawer({super.key}); + + @override + Widget build(BuildContext context) { + final authState = ClerkAuth.of(context); + + return Drawer( + child: ListView( + children: [ + const DrawerHeader( + child: Text('Menu'), + ), + if (authState.env.organization.isEnabled) + ListTile( + leading: const Icon(Icons.business), + title: const Text('Organizations'), + onTap: () { + Navigator.pop(context); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const Scaffold( + appBar: AppBar(title: Text('Organizations')), + body: ClerkOrganizationList(), + ), + ), + ); + }, + ), + ], + ), + ); + } +} +``` + +### With Current Organization Display + +```dart +class OrganizationHeader extends StatelessWidget { + const OrganizationHeader({super.key}); + + @override + Widget build(BuildContext context) { + final authState = ClerkAuth.of(context); + final org = authState.organization; + + return ListTile( + leading: const Icon(Icons.business), + title: Text(org?.name ?? 'No Organization'), + subtitle: const Text('Tap to switch'), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const Scaffold( + body: ClerkOrganizationList(), + ), + ), + ); + }, + ); + } +} +``` + +--- + +## Checking Organization Support + +```dart +final authState = ClerkAuth.of(context); + +// Check if organizations are enabled +if (authState.env.organization.isEnabled) { + // Show organization features +} + +// Check if user can create organizations +if (authState.user?.createOrganizationEnabled == true) { + // Show create button +} + +// Get current organization +final currentOrg = authState.organization; +``` + +--- + +## Related Documentation + +- [ClerkAuth](clerk_auth.md) +- [ClerkUserButton](clerk_user_button.md) +- [Auth - Organization Management](../clerk_auth/auth.md#organization-management) + +--- + +*Generated for clerk_flutter version 0.0.14-beta* + diff --git a/docs/clerk_flutter/clerk_signed_in.md b/docs/clerk_flutter/clerk_signed_in.md new file mode 100644 index 00000000..c8223bb6 --- /dev/null +++ b/docs/clerk_flutter/clerk_signed_in.md @@ -0,0 +1,231 @@ +# ClerkSignedIn Widget Documentation + +The `ClerkSignedIn` widget conditionally renders its child only when a user is signed in. + +## Overview + +`ClerkSignedIn` is a simple conditional widget that shows its child when `authState.user` is not null. It automatically rebuilds when authentication state changes. + +## Class Definition + +```dart +class ClerkSignedIn extends StatefulWidget { + const ClerkSignedIn({ + super.key, + required this.child, + }); +} +``` + +--- + +## Parameters + +### `child` + +**Type:** `Widget` (required) + +The widget to display when a user is signed in. + +**Example:** +```dart +ClerkSignedIn( + child: Text('You are signed in!'), +) +``` + +--- + +## Behavior + +- **When signed in** (user is not null): Renders the `child` widget +- **When signed out** (user is null): Renders an empty widget (nothing) + +--- + +## Complete Examples + +### Basic Usage + +```dart +class HomePage extends StatelessWidget { + const HomePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Home')), + body: Column( + children: [ + ClerkSignedIn( + child: Text('Welcome back!'), + ), + ClerkSignedOut( + child: Text('Please sign in'), + ), + ], + ), + ); + } +} +``` + +### Protected Content + +```dart +class ProtectedPage extends StatelessWidget { + const ProtectedPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Protected Page'), + ), + body: ClerkSignedIn( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('This is protected content'), + const SizedBox(height: 20), + ElevatedButton( + onPressed: () { + final authState = ClerkAuth.of(context, listen: false); + authState.signOut(); + }, + child: const Text('Sign Out'), + ), + ], + ), + ), + ), + ); + } +} +``` + +### Conditional AppBar Actions + +```dart +class MyScaffold extends StatelessWidget { + const MyScaffold({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('My App'), + actions: [ + ClerkSignedIn( + child: const ClerkUserButton(), + ), + ], + ), + body: const Center(child: Text('Content')), + ); + } +} +``` + +### Navigation Drawer + +```dart +class MyDrawer extends StatelessWidget { + const MyDrawer({super.key}); + + @override + Widget build(BuildContext context) { + return Drawer( + child: ListView( + children: [ + const DrawerHeader( + child: Text('Menu'), + ), + ListTile( + leading: const Icon(Icons.home), + title: const Text('Home'), + onTap: () => Navigator.pop(context), + ), + ClerkSignedIn( + child: ListTile( + leading: const Icon(Icons.person), + title: const Text('Profile'), + onTap: () { + Navigator.pop(context); + Navigator.pushNamed(context, '/profile'); + }, + ), + ), + ClerkSignedIn( + child: ListTile( + leading: const Icon(Icons.settings), + title: const Text('Settings'), + onTap: () { + Navigator.pop(context); + Navigator.pushNamed(context, '/settings'); + }, + ), + ), + ], + ), + ); + } +} +``` + +### With User Data + +```dart +class UserGreeting extends StatelessWidget { + const UserGreeting({super.key}); + + @override + Widget build(BuildContext context) { + return ClerkSignedIn( + child: Builder( + builder: (context) { + final user = ClerkAuth.userOf(context); + return Text('Hello, ${user?.firstName ?? "User"}!'); + }, + ), + ); + } +} +``` + +--- + +## Best Practices + +1. **Use with ClerkSignedOut**: Pair with `ClerkSignedOut` to cover both states +2. **Keep it simple**: Use for simple conditional rendering; use `ClerkAuthBuilder` for complex logic +3. **Avoid nesting**: Don't nest multiple `ClerkSignedIn` widgets unnecessarily +4. **Consider loading state**: This widget doesn't handle loading state; use `ClerkAuthBuilder` if needed + +--- + +## Comparison with ClerkAuthBuilder + +**Use ClerkSignedIn when:** +- You only need to show/hide content based on sign-in state +- You want simple, declarative code +- You're building a list of conditional widgets + +**Use ClerkAuthBuilder when:** +- You need access to the full `authState` +- You want to handle both signed-in and signed-out states differently +- You need to handle loading or other intermediate states + +--- + +## Related Documentation + +- [ClerkSignedOut](clerk_signed_out.md) +- [ClerkAuthBuilder](clerk_auth_builder.md) +- [ClerkAuth](clerk_auth.md) + +--- + +*Generated for clerk_flutter version 0.0.14-beta* + diff --git a/docs/clerk_flutter/clerk_signed_out.md b/docs/clerk_flutter/clerk_signed_out.md new file mode 100644 index 00000000..0d322cce --- /dev/null +++ b/docs/clerk_flutter/clerk_signed_out.md @@ -0,0 +1,171 @@ +# ClerkSignedOut Widget Documentation + +The `ClerkSignedOut` widget conditionally renders its child only when no user is signed in. + +## Overview + +`ClerkSignedOut` is the complement to `ClerkSignedIn`. It shows its child when `authState.user` is null and automatically rebuilds when authentication state changes. + +## Class Definition + +```dart +class ClerkSignedOut extends StatefulWidget { + const ClerkSignedOut({ + super.key, + required this.child, + }); +} +``` + +--- + +## Parameters + +### `child` + +**Type:** `Widget` (required) + +The widget to display when no user is signed in. + +**Example:** +```dart +ClerkSignedOut( + child: Text('Please sign in'), +) +``` + +--- + +## Behavior + +- **When signed out** (user is null): Renders the `child` widget +- **When signed in** (user is not null): Renders an empty widget (nothing) + +--- + +## Complete Examples + +### Basic Usage + +```dart +class HomePage extends StatelessWidget { + const HomePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ClerkSignedOut( + child: const ClerkAuthentication(), + ), + ClerkSignedIn( + child: const Text('You are signed in!'), + ), + ], + ), + ), + ); + } +} +``` + +### Login Page + +```dart +class LoginPage extends StatelessWidget { + const LoginPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: ClerkSignedOut( + child: const Center( + child: ClerkAuthentication(), + ), + ), + ); + } +} +``` + +### Conditional Navigation + +```dart +class AuthGate extends StatelessWidget { + const AuthGate({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + ClerkSignedOut( + child: ElevatedButton( + onPressed: () => Navigator.pushNamed(context, '/login'), + child: const Text('Sign In'), + ), + ), + ClerkSignedIn( + child: ElevatedButton( + onPressed: () => Navigator.pushNamed(context, '/dashboard'), + child: const Text('Go to Dashboard'), + ), + ), + ], + ); + } +} +``` + +### AppBar Actions + +```dart +class MyAppBar extends StatelessWidget implements PreferredSizeWidget { + const MyAppBar({super.key}); + + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight); + + @override + Widget build(BuildContext context) { + return AppBar( + title: const Text('My App'), + actions: [ + ClerkSignedOut( + child: TextButton( + onPressed: () => Navigator.pushNamed(context, '/login'), + child: const Text('Sign In'), + ), + ), + ClerkSignedIn( + child: const ClerkUserButton(), + ), + ], + ); + } +} +``` + +--- + +## Best Practices + +1. **Use with ClerkSignedIn**: Pair with `ClerkSignedIn` to cover both states +2. **Show authentication UI**: Commonly used to display `ClerkAuthentication` widget +3. **Keep it simple**: Use for simple conditional rendering +4. **Consider user experience**: Provide clear calls-to-action for signing in + +--- + +## Related Documentation + +- [ClerkSignedIn](clerk_signed_in.md) +- [ClerkAuthBuilder](clerk_auth_builder.md) +- [ClerkAuthentication](clerk_authentication.md) + +--- + +*Generated for clerk_flutter version 0.0.14-beta* + diff --git a/docs/clerk_flutter/clerk_theme.md b/docs/clerk_flutter/clerk_theme.md new file mode 100644 index 00000000..7c422f7b --- /dev/null +++ b/docs/clerk_flutter/clerk_theme.md @@ -0,0 +1,267 @@ +# ClerkTheme Documentation + +The `ClerkThemeExtension` class provides theming capabilities for Clerk widgets in Flutter. + +## Overview + +`ClerkThemeExtension` is a Flutter `ThemeExtension` that allows you to customize the appearance of all Clerk widgets including colors, text styles, and component styles. + +## Class Definition + +```dart +class ClerkThemeExtension extends ThemeExtension { + const ClerkThemeExtension({ + required this.colors, + this.styles, + }); + + final ClerkThemeColors colors; + final ClerkThemeStyles? styles; +} +``` + +--- + +## ClerkThemeColors + +```dart +class ClerkThemeColors { + const ClerkThemeColors({ + required this.background, + required this.altBackground, + required this.borderSide, + required this.text, + required this.icon, + required this.lightweightText, + required this.error, + required this.accent, + }); + + final Color background; + final Color altBackground; + final Color borderSide; + final Color text; + final Color icon; + final Color lightweightText; + final Color error; + final Color accent; +} +``` + +--- + +## ClerkThemeStyles + +```dart +class ClerkThemeStyles { + const ClerkThemeStyles({ + this.heading, + this.subheading, + this.text, + this.lightweightText, + this.button, + }); + + final TextStyle? heading; + final TextStyle? subheading; + final TextStyle? text; + final TextStyle? lightweightText; + final TextStyle? button; +} +``` + +--- + +## Complete Examples + +### Light Theme + +```dart +MaterialApp( + theme: ThemeData.light().copyWith( + extensions: [ + ClerkThemeExtension( + colors: ClerkThemeColors( + background: Colors.white, + altBackground: Colors.grey[100]!, + borderSide: Colors.grey[300]!, + text: Colors.black87, + icon: Colors.grey[600]!, + lightweightText: Colors.grey[500]!, + error: Colors.red[700]!, + accent: Colors.blue[600]!, + ), + ), + ], + ), + // ... rest of app +) +``` + +### Dark Theme + +```dart +MaterialApp( + darkTheme: ThemeData.dark().copyWith( + extensions: [ + ClerkThemeExtension( + colors: ClerkThemeColors( + background: Colors.grey[900]!, + altBackground: Colors.grey[850]!, + borderSide: Colors.grey[700]!, + text: Colors.white, + icon: Colors.grey[400]!, + lightweightText: Colors.grey[500]!, + error: Colors.red[400]!, + accent: Colors.blue[400]!, + ), + ), + ], + ), + themeMode: ThemeMode.dark, + // ... rest of app +) +``` + +### Custom Brand Colors + +```dart +const brandPrimary = Color(0xFF6366F1); // Indigo +const brandSecondary = Color(0xFF8B5CF6); // Purple + +MaterialApp( + theme: ThemeData.light().copyWith( + extensions: [ + ClerkThemeExtension( + colors: ClerkThemeColors( + background: Colors.white, + altBackground: const Color(0xFFF9FAFB), + borderSide: const Color(0xFFE5E7EB), + text: const Color(0xFF111827), + icon: const Color(0xFF6B7280), + lightweightText: const Color(0xFF9CA3AF), + error: const Color(0xFFEF4444), + accent: brandPrimary, + ), + styles: ClerkThemeStyles( + heading: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Color(0xFF111827), + ), + subheading: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Color(0xFF374151), + ), + text: const TextStyle( + fontSize: 14, + color: Color(0xFF111827), + ), + button: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + ), + ], + ), +) +``` + +### With Custom Fonts + +```dart +ClerkThemeExtension( + colors: ClerkThemeColors( + // ... colors + ), + styles: ClerkThemeStyles( + heading: GoogleFonts.inter( + fontSize: 24, + fontWeight: FontWeight.bold, + ), + subheading: GoogleFonts.inter( + fontSize: 18, + fontWeight: FontWeight.w600, + ), + text: GoogleFonts.inter( + fontSize: 14, + ), + button: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), +) +``` + +--- + +## Accessing Theme in Widgets + +```dart +class MyWidget extends StatelessWidget { + const MyWidget({super.key}); + + @override + Widget build(BuildContext context) { + final theme = ClerkAuth.themeExtensionOf(context); + + return Container( + decoration: BoxDecoration( + color: theme.colors.background, + border: Border.all(color: theme.colors.borderSide), + ), + child: Column( + children: [ + Text( + 'Heading', + style: theme.styles.heading, + ), + Text( + 'Body text', + style: theme.styles.text, + ), + ], + ), + ); + } +} +``` + +--- + +## Color Properties + +- **background**: Main background color for panels and cards +- **altBackground**: Alternative background for sections +- **borderSide**: Border color for inputs and dividers +- **text**: Primary text color +- **icon**: Icon color +- **lightweightText**: Secondary/muted text color +- **error**: Error message and validation color +- **accent**: Primary action color (buttons, links) + +--- + +## Best Practices + +1. **Match your app theme**: Keep Clerk widgets consistent with your app +2. **Support dark mode**: Provide both light and dark themes +3. **Test contrast**: Ensure text is readable on backgrounds +4. **Use semantic colors**: Don't use accent color for errors +5. **Consider accessibility**: Follow WCAG guidelines for color contrast + +--- + +## Related Documentation + +- [ClerkAuth](clerk_auth.md) +- [ClerkAuthentication](clerk_authentication.md) + +--- + +*Generated for clerk_flutter version 0.0.14-beta* + diff --git a/docs/clerk_flutter/clerk_user_button.md b/docs/clerk_flutter/clerk_user_button.md new file mode 100644 index 00000000..85a7e435 --- /dev/null +++ b/docs/clerk_flutter/clerk_user_button.md @@ -0,0 +1,348 @@ +# ClerkUserButton Widget Documentation + +The `ClerkUserButton` widget renders a user profile button with multi-session management, profile access, and sign-out functionality. + +## Overview + +`ClerkUserButton` displays the current user's avatar and provides access to: +- User profile management +- Multi-session switching +- Organization management +- Sign-out functionality +- Custom actions + +## Class Definition + +```dart +class ClerkUserButton extends StatefulWidget { + const ClerkUserButton({ + super.key, + this.showName = true, + this.sessionActions, + this.additionalActions, + }); +} +``` + +--- + +## Parameters + +### `showName` + +**Type:** `bool` +**Default:** `true` + +Whether to display the user's name next to the avatar. + +**Example:** +```dart +ClerkUserButton(showName: false) // Avatar only +``` + +--- + +### `sessionActions` + +**Type:** `List?` + +Custom actions to display for each session. If null, uses default actions (Profile, Sign Out, Organizations). + +**Example:** +```dart +ClerkUserButton( + sessionActions: [ + ClerkUserAction( + icon: Icons.settings, + label: 'Settings', + callback: (context, authState) { + Navigator.pushNamed(context, '/settings'); + }, + ), + ], +) +``` + +--- + +### `additionalActions` + +**Type:** `List?` + +Additional actions to display in the user panel. If null, uses default action (Add Account). + +**Example:** +```dart +ClerkUserButton( + additionalActions: [ + ClerkUserAction( + icon: Icons.help, + label: 'Help', + callback: (context, authState) { + // Show help + }, + ), + ], +) +``` + +--- + +## ClerkUserAction + +```dart +class ClerkUserAction { + ClerkUserAction({ + this.icon, + this.asset, + required this.label, + required this.callback, + }); + + final IconData? icon; + final String? asset; + final String label; + final Future Function(BuildContext, ClerkAuthState) callback; +} +``` + +--- + +## Complete Examples + +### Basic Usage + +```dart +class MyAppBar extends StatelessWidget implements PreferredSizeWidget { + const MyAppBar({super.key}); + + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight); + + @override + Widget build(BuildContext context) { + return AppBar( + title: const Text('My App'), + actions: const [ + ClerkUserButton(), + ], + ); + } +} +``` + +### Avatar Only + +```dart +AppBar( + actions: const [ + ClerkUserButton(showName: false), + ], +) +``` + +### Custom Session Actions + +```dart +ClerkUserButton( + sessionActions: [ + ClerkUserAction( + icon: Icons.person, + label: 'Profile', + callback: (context, authState) async { + Navigator.pushNamed(context, '/profile'); + }, + ), + ClerkUserAction( + icon: Icons.settings, + label: 'Settings', + callback: (context, authState) async { + Navigator.pushNamed(context, '/settings'); + }, + ), + ClerkUserAction( + icon: Icons.logout, + label: 'Sign Out', + callback: (context, authState) async { + await authState.signOut(); + }, + ), + ], +) +``` + +### With Additional Actions + +```dart +ClerkUserButton( + additionalActions: [ + ClerkUserAction( + icon: Icons.add, + label: 'Add Account', + callback: (context, authState) async { + // Add account logic + }, + ), + ClerkUserAction( + icon: Icons.help, + label: 'Help & Support', + callback: (context, authState) async { + Navigator.pushNamed(context, '/help'); + }, + ), + ], +) +``` + +### In Drawer + +```dart +class MyDrawer extends StatelessWidget { + const MyDrawer({super.key}); + + @override + Widget build(BuildContext context) { + return Drawer( + child: ListView( + children: [ + DrawerHeader( + child: ClerkSignedIn( + child: const ClerkUserButton(), + ), + ), + ListTile( + leading: const Icon(Icons.home), + title: const Text('Home'), + onTap: () => Navigator.pop(context), + ), + // ... more items + ], + ), + ); + } +} +``` + +--- + +## Features + +### Multi-Session Support + +When multiple sessions exist, the button displays all sessions and allows switching between them: + +```dart +// Users can: +// 1. See all active sessions +// 2. Click to switch between sessions +// 3. Sign out of individual sessions +// 4. Add new accounts +``` + +### Profile Management + +Clicking the profile action opens the user profile panel where users can: +- Edit name and username +- Manage email addresses +- Manage phone numbers +- Add/remove passkeys +- Connect/disconnect OAuth accounts +- Update profile image + +### Organization Management + +If organizations are enabled, users can: +- View their organizations +- Switch active organization +- Create new organizations +- Accept invitations + +--- + +## Default Actions + +### Session Actions (default) + +1. **Profile** - Opens user profile panel +2. **Sign Out** - Signs out of the current session +3. **Organizations** - Opens organization list (if enabled) + +### Additional Actions (default) + +1. **Add Account** - Adds a new account (if multi-session enabled) + +--- + +## Best Practices + +1. **Use in signed-in areas**: Only show when user is signed in +2. **Place in AppBar**: Common pattern is to place in AppBar actions +3. **Customize for your app**: Add custom actions relevant to your app +4. **Test multi-session**: Test with multiple accounts if multi-session is enabled +5. **Consider mobile**: The button adapts to mobile screens + +--- + +## Common Patterns + +### Conditional Display + +```dart +AppBar( + actions: [ + ClerkSignedIn( + child: const ClerkUserButton(), + ), + ClerkSignedOut( + child: TextButton( + onPressed: () => Navigator.pushNamed(context, '/login'), + child: const Text('Sign In'), + ), + ), + ], +) +``` + +### With Navigation + +```dart +ClerkUserButton( + sessionActions: [ + ClerkUserAction( + icon: Icons.dashboard, + label: 'Dashboard', + callback: (context, authState) async { + Navigator.pushNamed(context, '/dashboard'); + }, + ), + ClerkUserAction( + icon: Icons.person, + label: 'Profile', + callback: (context, authState) async { + Navigator.pushNamed(context, '/profile'); + }, + ), + ClerkUserAction( + icon: Icons.logout, + label: 'Sign Out', + callback: (context, authState) async { + await authState.signOut(); + Navigator.pushReplacementNamed(context, '/'); + }, + ), + ], +) +``` + +--- + +## Related Documentation + +- [ClerkAuth](clerk_auth.md) +- [ClerkSignedIn](clerk_signed_in.md) +- [ClerkOrganizationList](clerk_organization_list.md) + +--- + +*Generated for clerk_flutter version 0.0.14-beta* + diff --git a/packages/clerk_auth/README.md b/packages/clerk_auth/README.md index 8a951f73..a9db2360 100644 --- a/packages/clerk_auth/README.md +++ b/packages/clerk_auth/README.md @@ -4,21 +4,25 @@ ## Official [Clerk](https://clerk.com) Dart SDK (Beta) -[![Pub Version](https://img.shields.io/pub/v/clerk_auth?color=blueviolet)](https://pub.dev/packages/clerk_auth) +[![Pub Version](https://img.shields.io/badge/pub-v0.0.14--beta-blueviolet)](https://pub.dev/packages/clerk_auth) [![Pub Points](https://img.shields.io/pub/points/clerk_auth?label=pub%20points)](https://pub.dev/packages/clerk_auth/score) -[![chat on Discord](https://img.shields.io/discord/856971667393609759.svg?logo=discord)](https://discord.com/invite/b5rXHjAg7A) +[![chat on Discord](https://img.shields.io/discord/856971667393609759.svg?logo=discord)](https://clerk.com/discord) [![documentation](https://img.shields.io/badge/documentation-clerk-green.svg)](https://clerk.com/docs) -[![twitter](https://img.shields.io/twitter/follow/ClerkDev?style=social)](https://twitter.com/intent/follow?screen_name=ClerkDev) +[![twitter](https://img.shields.io/twitter/follow/Clerk?style=social)](https://twitter.com/intent/follow?screen_name=Clerk) -> ### ⚠️ The Clerk Flutter SDK is in Beta ⚠️ -> ❗️ Breaking changes should be expected until the first stable release (1.0.0) ❗️ +> ### ⚠️ The Clerk Dart SDK is in Beta ⚠️ +> ❗️ Breaking changes should be expected until the first stable release (1.0.0). Please [file any issues you encounter](https://github.com/clerk/clerk-sdk-flutter/issues). ❗️ **Clerk helps developers build user management. We provide streamlined user experiences for your users to sign up, sign in, and manage their profile from your Dart code.** +--- + ## Requirements -* Dart >= 3.6.2 +| Dart | +|------| +| 3.6.2+ | ## Example Usage @@ -68,7 +72,9 @@ Future main() async { } ``` -For more details see [Clerk Auth object](https://pub.dev/documentation/clerk_auth/latest/clerk_auth/Auth-class.html) +For more details see: +- [Clerk Auth Documentation](https://clerk.github.io/clerk-sdk-flutter/packages/clerk-auth) +- [API Reference](https://pub.dev/documentation/clerk_auth/latest/clerk_auth/Auth-class.html) ## Session Token Polling @@ -97,6 +103,20 @@ polling off, enabling it via a `SessionTokenPollMode` object in the config. This v0.0.13-beta: it is no longer used, and will be deleted in a future version. If you are using it, or relying on the system defaulting to no polling, please review your code. +--- + +## πŸ“– Learn More + +- [Full Documentation](https://clerk.github.io/clerk-sdk-flutter/) +- [Dart Quickstart](https://clerk.github.io/clerk-sdk-flutter/getting-started/quickstart-dart) +- [Authentication Guide](https://clerk.github.io/clerk-sdk-flutter/guides/authentication) +- [User Management](https://clerk.github.io/clerk-sdk-flutter/guides/user-management) +- [Session Tokens](https://clerk.github.io/clerk-sdk-flutter/guides/session-tokens) + +--- + ## License -This SDK is licensed under the MIT license found in the [LICENSE](./LICENSE) file. +This project is licensed under the MIT license. + +See [LICENSE](./LICENSE) for more information. diff --git a/packages/clerk_flutter/README.md b/packages/clerk_flutter/README.md index b54d1aec..a261b131 100644 --- a/packages/clerk_flutter/README.md +++ b/packages/clerk_flutter/README.md @@ -2,37 +2,50 @@

-## Official [Clerk](https://clerk.com) Flutter SDK (Beta) +## Official Clerk Flutter SDK (Beta) -[![Pub Version](https://img.shields.io/pub/v/clerk_flutter?color=blueviolet)](https://pub.dev/packages/clerk_flutter) +[![Pub Version](https://img.shields.io/badge/pub-v0.0.14--beta-blueviolet)](https://pub.dev/packages/clerk_flutter) [![Pub Points](https://img.shields.io/pub/points/clerk_flutter?label=pub%20points)](https://pub.dev/packages/clerk_flutter/score) -[![chat on Discord](https://img.shields.io/discord/856971667393609759.svg?logo=discord)](https://discord.com/invite/b5rXHjAg7A) +[![chat on Discord](https://img.shields.io/discord/856971667393609759.svg?logo=discord)](https://clerk.com/discord) [![documentation](https://img.shields.io/badge/documentation-clerk-green.svg)](https://clerk.com/docs) -[![twitter](https://img.shields.io/twitter/follow/ClerkDev?style=social)](https://twitter.com/intent/follow?screen_name=ClerkDev) +[![twitter](https://img.shields.io/twitter/follow/Clerk?style=social)](https://twitter.com/intent/follow?screen_name=Clerk) > ### ⚠️ The Clerk Flutter SDK is in Beta ⚠️ -> ❗️ Breaking changes should be expected until the first stable release (1.0.0) ❗️ +> ❗️ Breaking changes should be expected until the first stable release (1.0.0). Please [file any issues you encounter](https://github.com/clerk/clerk-sdk-flutter/issues). ❗️ **Clerk helps developers build user management. We provide streamlined user experiences for your users to sign up, sign in, and manage their profile from your Flutter code.** +

+ + +
+ The clerk_flutter example app +

+ +--- + ## Requirements -* Flutter >= 3.27.4 -* Dart >= 3.6.2 +| Flutter | Dart | +|---------|------| +| 3.27.4+ | 3.6.2+ | -## In Development +## πŸš€ Getting Started -* Organization support +1. [Sign up for an account](https://dashboard.clerk.com/sign-up?utm_source=github&utm_medium=clerk_flutter_repo_readme) +2. Create an application in your Clerk dashboard +3. Copy the publishable key from the dashboard +4. Add `clerk_flutter` and `clerk_auth` to your `pubspec.yaml` -## Example Usage +```yaml +dependencies: + clerk_auth: ^0.0.14-beta + clerk_flutter: ^0.0.14-beta +``` -To use this package you will need to go to your [Clerk Dashboard](https://dashboard.clerk.com/) -create an application and copy the public and publishable API keys into your project. +5. You can now make use of Clerk Flutter widgets adding authentication to your application -The bundled example app requires one, possibly two, variables to be set up in your environment: -- `publishable_key`: your Clerk publishable key, usually starting `pk_` -- `google_client_id`: the ID of your GCP web project, if you are using Google token oauth ```dart /// Example App @@ -52,15 +65,13 @@ class ExampleApp extends StatelessWidget { debugShowCheckedModeBanner: false, home: Scaffold( body: SafeArea( - child: ClerkErrorListener( - child: ClerkAuthBuilder( - signedInBuilder: (context, authState) { - return const ClerkUserButton(); - }, - signedOutBuilder: (context, authState) { - return const ClerkAuthentication(); - }, - ), + child: ClerkAuthBuilder( + signedInBuilder: (context, authState) { + return const ClerkUserButton(); + }, + signedOutBuilder: (context, authState) { + return const ClerkAuthentication(); + }, ), ), ), @@ -70,14 +81,8 @@ class ExampleApp extends StatelessWidget { } ``` -## Installation (Android) - -Add the following line to your `android/app/src/main/AndroidManifest.xml` file: - -```xml - -``` - ## License -This SDK is licensed under the MIT license found in the [LICENSE](./LICENSE) file. +This project is licensed under the MIT license. + +See [LICENSE](./LICENSE) for more information. diff --git a/packages/clerk_flutter/doc/clerk-ui/clerk-authentication.mdx b/packages/clerk_flutter/doc/clerk-ui/clerk-authentication.mdx new file mode 100644 index 00000000..797a2683 --- /dev/null +++ b/packages/clerk_flutter/doc/clerk-ui/clerk-authentication.mdx @@ -0,0 +1,78 @@ +--- +title: '`ClerkAuthentication`' +description: Clerk's ClerkAuthentication widget renders a UI for signing in and signing up users in your Flutter app. +sdk: flutter +--- + +[//]: # (![The ClerkAuthentication widget renders a comprehensive authentication interface that handles both user sign-in and sign-up flows.](/docs/images/ui-components/flutter-clerk-authentication.png){{ style: { maxWidth: '460px' } }}) + +The `ClerkAuthentication` widget renders a comprehensive authentication interface with support for multiple sign-up flows and sign-in methods, multi-factor authentication, password reset, and account recovery. The functionality of the `ClerkAuthentication` widget is controlled by the instance settings you specify in the [Clerk Dashboard](https://dashboard.clerk.com), such as [sign-in and sign-up options](/docs/guides/configure/auth-strategies/sign-up-sign-in-options) and [social connections](/docs/guides/configure/auth-strategies/social-connections/overview). + +By default, `ClerkAuthentication` automatically determines whether to sign users in or sign them up based on whether they already have an account, providing a seamless authentication experience without requiring users to choose between the two flows. + +`ClerkAuthentication` must be placed somewhere below a [`ClerkAuth`](/docs/flutter/getting-started/clerk-auth) widget in the widget tree. + +## Parameters + +`ClerkAuthentication` has no additional parameters beyond the standard Flutter `key`. + +## Usage + +### Basic usage + +The following example shows how to embed `ClerkAuthentication` in a screen that conditionally shows the authentication widget or the signed-in content. + +```dart +import 'package:clerk_flutter/clerk_flutter.dart'; +import 'package:flutter/material.dart'; + +class HomeScreen extends StatelessWidget { + const HomeScreen({super.key}); + + @override + Widget build(BuildContext context) { + final user = ClerkAuth.userOf(context); + + if (user != null) { + return const Text('You are signed in!'); + } + + return const ClerkAuthentication(); + } +} +``` + +### Present as a modal sheet + +The following example shows how to present `ClerkAuthentication` as a modal bottom sheet when the user taps a button. + +```dart +import 'package:clerk_flutter/clerk_flutter.dart'; +import 'package:flutter/material.dart'; + +class HomeScreen extends StatelessWidget { + const HomeScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: ElevatedButton( + onPressed: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (_) => const ClerkAuthentication(), + ); + }, + child: const Text('Sign in'), + ), + ), + ); + } +} +``` + +## Customization + + diff --git a/packages/clerk_flutter/doc/clerk-ui/customization.mdx b/packages/clerk_flutter/doc/clerk-ui/customization.mdx new file mode 100644 index 00000000..8cf68f92 --- /dev/null +++ b/packages/clerk_flutter/doc/clerk-ui/customization.mdx @@ -0,0 +1,310 @@ +--- +title: '`ClerkThemeExtension`' +description: Utilize Clerk's ClerkThemeExtension to adjust the general styles of the Clerk widgets, like colors and text styles. +sdk: flutter +--- + +The `ClerkThemeExtension` is used to customize the appearance of Clerk widgets by adjusting colors and text styles. It integrates with Flutter's `ThemeData` extension system to provide a consistent visual experience across all Clerk widgets. + +## Structure + +`ClerkThemeExtension` consists of two main properties: + +- **`colors`** - Color properties for various UI elements. +- **`stylesBuilder`** - A builder function that produces text styles from the color palette. Defaults to `ClerkThemeStyles.defaultStylesBuilder`. + +## Colors + +The `colors` property is a `ClerkThemeColors` object containing the following color options: + + + - `background` + - `Color` + + The main background color of Clerk widgets. Light theme default: `Colors.white`. Dark theme default: `Colors.black`. + + --- + + - `altBackground` + - `Color` + + An alternative background color used for secondary surfaces. Light theme default: `Color(0xFFdddddd)`. Dark theme default: `Color(0xFF333333)`. + + --- + + - `borderSide` + - `Color` + + The color used for borders and dividers. Light theme default: `Color(0xFFdddddd)`. Dark theme default: `Color(0xFF333333)`. + + --- + + - `text` + - `Color` + + The primary text color. Light theme default: `Color(0xFF3c3c3d)`. Dark theme default: `Colors.white`. + + --- + + - `icon` + - `Color` + + The color used for icons. Light theme default: `Color(0xFFaaaaaa)`. Dark theme default: `Colors.white`. + + --- + + - `lightweightText` + - `Color` + + The color used for secondary or hint text. Light theme default: `Color(0xFFaaaaaa)`. Dark theme default: `Color(0xFF555555)`. + + --- + + - `error` + - `Color` + + The color used for error messages and validation states. Default: `Color(0xFFff3333)` (both themes). + + --- + + - `accent` + - `Color` + + The color used for links, buttons, and interactive elements. Default: `Color(0xFF6c47ff)` (both themes). + + +## Text styles + +The computed `styles` property is a `ClerkThemeStyles` object containing the following text styles, derived automatically from `colors`: + + + - `heading` + - `TextStyle` + + Used for main headings. Font size: `18.0`, weight: `w500`, color: `colors.text`. + + --- + + - `subheading` + - `TextStyle` + + Used for secondary headings. Font size: `14.0`, weight: `w400`, color: `colors.text`. + + --- + + - `inputText` + - `TextStyle` + + Used for form field labels and hints. Font size: `14.0`, color: `colors.lightweightText`. + + --- + + - `error` + - `TextStyle` + + Used for validation error messages. Font size: `14.0`, color: `colors.error`. + + --- + + - `text` + - `TextStyle` + + Used for regular body text. Font size: `12.0`, color: `colors.text`. + + --- + + - `subtext` + - `TextStyle` + + Used for small helper text. Font size: `10.0`, color: `colors.lightweightText`. + + --- + + - `clickableText` + - `TextStyle` + + Used for links and tappable text. Font size: `12.0`, color: `colors.accent`. + + --- + + - `button` + - `TextStyle` + + Used for button labels. Font size: `12.0`, weight: `w500`, color: `colors.text`. + + --- + + - `avatarInitials` + - `TextStyle` + + Used for user avatar initials. Font size: `14.0`, color: `colors.background`. + + +## Usage + +Clerk widgets automatically inherit the `ClerkThemeExtension` from the nearest `ThemeData` in the widget tree. If no extension is provided, Clerk falls back to a built-in light or dark preset based on the current `ThemeData.brightness`. + +### Apply a custom theme to all Clerk widgets + +To customize all Clerk widgets in your app, add a `ClerkThemeExtension` to your root `MaterialApp`'s theme extensions. + +```dart +import 'package:clerk_flutter/clerk_flutter.dart'; +import 'package:flutter/material.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + theme: ThemeData.light().copyWith( + extensions: [ + ClerkThemeExtension( + colors: const ClerkThemeColors( + background: Colors.white, + altBackground: Color(0xFFf5f5f5), + borderSide: Color(0xFFe0e0e0), + text: Color(0xFF1a1a1a), + icon: Color(0xFF757575), + lightweightText: Color(0xFF9e9e9e), + error: Color(0xFFd32f2f), + accent: Color(0xFF0057FF), + ), + ), + ], + ), + home: const RootPage(), + ); + } +} +``` + +### Apply a theme to specific widgets + +You can scope a `ClerkThemeExtension` to a subtree by wrapping it in a `Theme` widget. This is useful when different screens require different Clerk styles. + +```dart +import 'package:clerk_flutter/clerk_flutter.dart'; +import 'package:flutter/material.dart'; + +class SignInPage extends StatelessWidget { + const SignInPage({super.key}); + + @override + Widget build(BuildContext context) { + return Theme( + data: Theme.of(context).copyWith( + extensions: [ + ClerkThemeExtension( + colors: const ClerkThemeColors( + background: Colors.white, + altBackground: Color(0xFFf5f5f5), + borderSide: Color(0xFFe0e0e0), + text: Color(0xFF1a1a1a), + icon: Color(0xFF757575), + lightweightText: Color(0xFF9e9e9e), + error: Color(0xFFd32f2f), + accent: Colors.purple, + ), + ), + ], + ), + child: const ClerkAuthentication(), + ); + } +} +``` + +### Custom text styles + +You can override the default text styles by providing a custom `stylesBuilder`. This is useful when you want to use a custom font family or adjust specific text properties. + +```dart +import 'package:clerk_flutter/clerk_flutter.dart'; +import 'package:flutter/material.dart'; + +class CustomStylesApp extends StatelessWidget { + const CustomStylesApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + theme: ThemeData.light().copyWith( + extensions: [ + ClerkThemeExtension( + colors: const ClerkThemeColors( + background: Colors.white, + altBackground: Color(0xFFf5f5f5), + borderSide: Color(0xFFe0e0e0), + text: Color(0xFF1a1a1a), + icon: Color(0xFF757575), + lightweightText: Color(0xFF9e9e9e), + error: Color(0xFFd32f2f), + accent: Color(0xFF0057FF), + ), + stylesBuilder: (colors) => ClerkThemeStyles(colors: colors), + ), + ], + ), + home: const RootPage(), + ); + } +} +``` + +## Light and dark mode support + +Clerk widgets automatically support both light and dark mode, adapting to the active `ThemeData.brightness`. If your app provides separate `theme` and `darkTheme` values to `MaterialApp`, add a `ClerkThemeExtension` to each. + +```dart +import 'package:clerk_flutter/clerk_flutter.dart'; +import 'package:flutter/material.dart'; + +const _lightClerkTheme = ClerkThemeExtension( + colors: ClerkThemeColors( + background: Colors.white, + altBackground: Color(0xFFf5f5f5), + borderSide: Color(0xFFe0e0e0), + text: Color(0xFF1a1a1a), + icon: Color(0xFF757575), + lightweightText: Color(0xFF9e9e9e), + error: Color(0xFFd32f2f), + accent: Color(0xFF0057FF), + ), +); + +const _darkClerkTheme = ClerkThemeExtension( + colors: ClerkThemeColors( + background: Color(0xFF121212), + altBackground: Color(0xFF1e1e1e), + borderSide: Color(0xFF2c2c2c), + text: Colors.white, + icon: Color(0xFFbbbbbb), + lightweightText: Color(0xFF757575), + error: Color(0xFFef5350), + accent: Color(0xFF4d8fff), + ), +); + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + theme: ThemeData.light().copyWith( + extensions: [_lightClerkTheme], + ), + darkTheme: ThemeData.dark().copyWith( + extensions: [_darkClerkTheme], + ), + home: const RootPage(), + ); + } +} +``` diff --git a/packages/clerk_flutter/doc/clerk-ui/overview.mdx b/packages/clerk_flutter/doc/clerk-ui/overview.mdx new file mode 100644 index 00000000..1387c26c --- /dev/null +++ b/packages/clerk_flutter/doc/clerk-ui/overview.mdx @@ -0,0 +1,38 @@ +--- +title: Clerk UI overview +description: A list of Clerk's pre-built Flutter widgets designed to seamlessly integrate authentication and multi-tenancy into your application. +sdk: flutter +--- + +Clerk offers prebuilt Flutter widgets designed to seamlessly integrate authentication and multi-tenancy into your application. +With Clerk's UI widgets you can customise the appearance of authentication screens, manage the entire authentication flow to suit your specific needs, and build robust SaaS applications. + +## Clerk widgets + +- `ClerkAuthentication` β€” Renders a full authentication interface, supporting multiple sign-up and sign-in methods, +multi-factor authentication (MFA), and password recovery flows. +- `ClerkUserButton` β€” Displays the signed-in user's profile image along with controls to manage the account and sign +out. +- `ClerkUserProfile` β€” Renders a complete user profile interface with personal information, security settings, +account switching, and sign-out options. +- `ClerkOrganizationList` β€” Lists all organizations the signed-in user belongs to and provides controls to switch +between them or create new ones. + +## Customization + +To learn how to customise the Clerk widgets, see the dedicated guide. + +If the prebuilt widgets do not meet your specific needs or if you require more control over the logic, you can rebuild +the existing Clerk flows using `clerk_auth`. + + +### Secured by Clerk branding + + + +By default, Clerk displays a **Secured by Clerk** badge on Clerk widgets. You can remove this branding by following these steps: + +1. In the Clerk Dashboard, navigate to your application's [**Settings**](https://dashboard.clerk.com/~/settings). +1. Under **Branding**, toggle on the **Remove "Secured by Clerk" branding** option. + + diff --git a/packages/clerk_flutter/doc/clerk-ui/user-views/user-button.mdx b/packages/clerk_flutter/doc/clerk-ui/user-views/user-button.mdx new file mode 100644 index 00000000..576a3b74 --- /dev/null +++ b/packages/clerk_flutter/doc/clerk-ui/user-views/user-button.mdx @@ -0,0 +1,173 @@ +--- +title: '`ClerkUserButton`' +description: Clerk's ClerkUserButton renders a panel that displays the signed-in user's profile and provides account management controls. +sdk: flutter +--- + +[//]: # (![The ClerkUserButton renders a panel displaying the signed-in user's profile image, name, and account management options.](/docs/images/ui-components/flutter-clerk-user-button.png){{ style: { maxWidth: '460px' } }}) + +The `ClerkUserButton` renders a panel that lists all currently signed-in sessions and provides controls to manage +accounts and sign out. When a session row is selected, action buttons appear for managing the profile, signing out, +and accessing organizations (if enabled). When multiple sessions are active, a **Sign out of all accounts** button +is shown at the bottom. + +> [!IMPORTANT] +> The `ClerkUserButton` is intended for use when a user is signed in. Place it in a part of your widget tree that +> is only reachable after authentication. + +`ClerkUserButton` must be placed somewhere below a [`ClerkAuth`](/docs/flutter/getting-started/clerk-auth) widget in the widget tree. + +## Parameters + + + - `showName` + - `bool` + + Whether to display the user's name alongside their avatar in each session row. Defaults to `true`. + + --- + + - `sessionActions` + - `List?` + + A list of actions rendered as buttons within the selected session row. When not provided, defaults to **Profile**, **Sign out**, and (if organizations are enabled) **Organizations**. + + --- + + - `additionalActions` + - `List?` + + A list of actions rendered as rows below the session list. When not provided, defaults to **Add account** (only in multi-session mode). + + +### `ClerkUserAction` parameters + +Each entry in `sessionActions` and `additionalActions` is a `ClerkUserAction`: + + + - `label` + - `String` + + The text label displayed for the action. Required. + + --- + + - `callback` + - `FutureOr Function(BuildContext, ClerkAuthState)` + + The function invoked when the action is tapped. Receives the current `BuildContext` and `ClerkAuthState`. Required. + + --- + + - `icon` + - `IconData?` + + A Material icon to display alongside the label. + + --- + + - `asset` + - `String?` + + An SVG asset path to display as the action's icon. Takes precedence over `icon` when both are provided. + + +## Usage + +### Basic usage + +The following example shows how to render `ClerkUserButton` when a user is signed in. + +```dart +import 'package:clerk_flutter/clerk_flutter.dart'; +import 'package:flutter/material.dart'; + +class HomeScreen extends StatelessWidget { + const HomeScreen({super.key}); + + @override + Widget build(BuildContext context) { + final user = ClerkAuth.userOf(context); + + if (user == null) { + return const SizedBox.shrink(); + } + + return const ClerkUserButton(); + } +} +``` + +### In an app bar + +The following example shows how to place `ClerkUserButton` in an `AppBar` action. + +```dart +import 'package:clerk_flutter/clerk_flutter.dart'; +import 'package:flutter/material.dart'; + +class HomeScreen extends StatelessWidget { + const HomeScreen({super.key}); + + @override + Widget build(BuildContext context) { + final user = ClerkAuth.userOf(context); + + return Scaffold( + appBar: AppBar( + title: const Text('Home'), + actions: [ + if (user != null) const ClerkUserButton(), + ], + ), + body: const Center(child: Text('Welcome!')), + ); + } +} +``` + +### With custom actions + +The following example shows how to add a custom action to the session row and an additional action below the session list. + +```dart +import 'package:clerk_flutter/clerk_flutter.dart'; +import 'package:flutter/material.dart'; + +class HomeScreen extends StatelessWidget { + const HomeScreen({super.key}); + + @override + Widget build(BuildContext context) { + return ClerkUserButton( + sessionActions: [ + ClerkUserAction( + icon: Icons.settings, + label: 'Settings', + callback: (context, authState) async { + // Navigate to settings + }, + ), + ClerkUserAction( + icon: Icons.logout, + label: 'Sign out', + callback: (context, authState) => authState.signOut(), + ), + ], + additionalActions: [ + ClerkUserAction( + icon: Icons.help_outline, + label: 'Help', + callback: (context, authState) async { + // Open help page + }, + ), + ], + ); + } +} +``` + +## Customization + + diff --git a/packages/clerk_flutter/doc/clerk-ui/user-views/user-profile-views.mdx b/packages/clerk_flutter/doc/clerk-ui/user-views/user-profile-views.mdx new file mode 100644 index 00000000..19c46089 --- /dev/null +++ b/packages/clerk_flutter/doc/clerk-ui/user-views/user-profile-views.mdx @@ -0,0 +1,81 @@ +--- +title: '`ClerkUserProfile`' +description: Clerk's ClerkUserProfile widget renders a full-featured account management UI that allows users to manage their profile and security settings. +sdk: flutter +--- + +[//]: # (![The ClerkUserProfile widget renders a comprehensive user profile interface that displays user information and provides account management options.](/docs/images/ui-components/flutter-clerk-user-profile.png){{ style: { maxWidth: '460px' } }}) + +The `ClerkUserProfile` widget renders a comprehensive user profile interface that displays user information and provides account management options. It includes personal information, security settings, connected accounts, and passkey management. + +> [!IMPORTANT] +> The `ClerkUserProfile` widget only renders when there is a current user. Place it in a part of your widget tree +that is only reachable when a user is signed in, or guard it with a signed-in check. + +`ClerkUserProfile` must be placed somewhere below a [`ClerkAuth`](/docs/flutter/getting-started/clerk-auth) widget +in the widget tree. + +## Parameters + +`ClerkUserProfile` has no additional parameters beyond the standard Flutter `key`. + +## Usage + +### Basic usage + +The following example shows how to render `ClerkUserProfile` on a dedicated screen that is only shown when a user is signed in. + +```dart +import 'package:clerk_flutter/clerk_flutter.dart'; +import 'package:flutter/material.dart'; + +class ProfileScreen extends StatelessWidget { + const ProfileScreen({super.key}); + + @override + Widget build(BuildContext context) { + final user = ClerkAuth.userOf(context); + + if (user == null) { + return const SizedBox.shrink(); + } + + return const ClerkUserProfile(); + } +} +``` + +### Present as a modal sheet + +The following example shows how to present `ClerkUserProfile` as a modal bottom sheet when the user taps a button. + +```dart +import 'package:clerk_flutter/clerk_flutter.dart'; +import 'package:flutter/material.dart'; + +class HomeScreen extends StatelessWidget { + const HomeScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: ElevatedButton( + onPressed: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (_) => const ClerkUserProfile(), + ); + }, + child: const Text('Manage profile'), + ), + ), + ); + } +} +``` + +## Customization + + diff --git a/packages/clerk_flutter/doc/getting-started/clerk-auth.mdx b/packages/clerk_flutter/doc/getting-started/clerk-auth.mdx new file mode 100644 index 00000000..97f8b553 --- /dev/null +++ b/packages/clerk_flutter/doc/getting-started/clerk-auth.mdx @@ -0,0 +1,62 @@ +--- +title: ClerkAuth +description: Access sign-in methods and user state with ClerkAuth in your Flutter app +sdk: flutter +--- + +`ClerkAuth` is the root control widget that initialises the Clerk auth system and makes auth state available to the +widget tree beneath it. All other Clerk widgets and helpers depend on a `ClerkAuth` ancestor being present. + +--- + +## Access the ClerkAuthState state + +Call `ClerkAuth.of(context)` anywhere below a `ClerkAuth` widget to get the `ClerkAuthState` object. By default this +registers a rebuild dependency β€” the widget rebuilds whenever auth state changes. Pass `listen: false` to read the +state without subscribing to changes (useful outside of `build`). + +```dart +final auth = ClerkAuth.of(context); +``` + +Sign-in methods are available directly on the `ClerkAuthState` object returned by `ClerkAuth.of(context)`. + +```dart +final auth = ClerkAuth.of(context, listen: false); + +// Email / password sign-in +await auth.attemptSignIn( + strategy: Strategy.password, + identifier: 'user@example.com', + password: 'secret', +); + +// OAuth sign-in (opens the provider in a browser) +await auth.oauthSignIn(strategy: Strategy.oauth_google); + +// Sign out the current user +await auth.signOut(); + +// Sign out of a specific session +await auth.signOutOf(session); +``` + +--- + +## Access user state + +Use the static convenience methods or read directly from the auth state object. + +```dart +// Static helpers (listen = true by default) +final user = ClerkAuth.userOf(context); // clerk.User? +final session = ClerkAuth.sessionOf(context); // clerk.Session? + +// Or via the auth state object +final auth = ClerkAuth.of(context); +final user = auth.user; // clerk.User? +final session = auth.session; // clerk.Session? +final isSigningIn = auth.isSigningIn; // bool β€” true while a sign-in is in progress +``` + +--- diff --git a/packages/clerk_flutter/doc/getting-started/configure-the-sdk.mdx b/packages/clerk_flutter/doc/getting-started/configure-the-sdk.mdx new file mode 100644 index 00000000..24e05939 --- /dev/null +++ b/packages/clerk_flutter/doc/getting-started/configure-the-sdk.mdx @@ -0,0 +1,274 @@ +--- +title: Configure the SDK +description: Configure the Clerk Flutter SDK with your publishable key and options. +sdk: flutter +--- + +## Configuration options + +`ClerkAuthConfig` is the primary way to configure the Clerk Flutter SDK. It extends the core `AuthConfig` from +`clerk_auth` and adds Flutter-specific options. Pass it to `ClerkAuth` either directly or via +`ClerkAuth.materialAppBuilder `. + +You can use it to configure: + +- **Authentication** β€” your publishable key, test mode, and session token behaviour +- **Networking** β€” custom HTTP service, connection timeouts, and retry options +- **Persistence** β€” custom persistor for storing session state +- **Loading UI** β€” a widget shown while the SDK initialises +- **Localizations** β€” translated strings for each supported locale +- **OAuth / SSO** β€” redirect URI generation and deep link handling +- **File caching** β€” caching of remote assets such as avatars and logos +- **Telemetry** β€” endpoint, send period, and opt-out + +--- + +## Class definition + +```dart +class ClerkAuthConfig extends clerk.AuthConfig { + ClerkAuthConfig({ + // From AuthConfig (required) + required String publishableKey, + + // From AuthConfig (optional) + bool sessionTokenPolling, + bool isTestMode, + String? telemetryEndpoint, + Duration? telemetryPeriod, + Duration? clientRefreshPeriod, + HttpService? httpService, + Duration? httpConnectionTimeout, + RetryOptions retryOptions, + String? defaultSessionTokenTemplate, + Persistor? persistor, + ClerkSdkFlags flags, + + // Flutter-specific + Widget? loading, + ClerkRedirectUriGenerator? redirectionGenerator, + Stream? deepLinkStream, + LaunchMode defaultLaunchMode, + bool supportsHardwareSecurityKeys, + ClerkFileCache? fileCache, + ClerkSdkLocalizationsCollection? localizations, + ClerkSdkLocalizations? fallbackLocalization, + ClerkSdkGrammarCollection? grammars, + ClerkSdkGrammar? fallbackGrammar, + }); +} +``` + +--- + +## Parameters + +### `ClerkAuthConfig` + + + - `publishableKey` + - `String` + + Your Clerk publishable key from the Clerk Dashboard. + + --- + + - `loading` + - `Widget?` + + Widget displayed while the SDK is initialising. Pass `null` to defer the first frame entirely (the OS splash screen + stays visible) rather than showing a spinner. + + Default: A built-in loading spinner. + + --- + + - `sessionTokenPolling` + - `bool` + + Whether the SDK should regularly poll for a refreshed session token in the background. Disable this if you want to + manage token refresh yourself. + + Default: `true`. + + --- + + - `defaultSessionTokenTemplate` + - `String?` + + The name of a JWT template to use by default when fetching session tokens. When `null`, the default Clerk session + token is used. + + Default: `null`. + + --- + + - `persistor` + - `Persistor?` + + Override the default persistor used to store session state between app launches. Implement `clerk.Persistor` to + provide your own storage backend. + + Default: A file-based caching persistor. + + --- + + - `httpService` + - `HttpService?` + + Override the HTTP client used for all Clerk API calls. Useful for injecting mock clients in tests or adding custom + interceptors. + + Default: `DefaultHttpService`. + + --- + + - `httpConnectionTimeout` + - `Duration?` + + How long the SDK waits for an HTTP connection before timing out during a connectivity check. + + Default: `Duration(milliseconds: 500)`. + + --- + + - `retryOptions` + - `RetryOptions` + + Controls retry behaviour for failed API requests β€” number of attempts, delays, and back-off strategy. + + Default: `RetryOptions()` (from the `retry` package). + + --- + + - `clientRefreshPeriod` + - `Duration?` + + How often the SDK polls to refresh the client object. Set to `Duration.zero` to disable polling entirely. + + Default: `Duration(milliseconds: 9700)` (~10 s). + + --- + + - `telemetryEndpoint` + - `String?` + + The URL telemetry data is sent to. + + Default: `'https://clerk-telemetry.com/v1/event'`. + + --- + + - `telemetryPeriod` + - `Duration?` + + How often telemetry data is sent. Set to `Duration.zero` to disable telemetry sending. + + Default: `Duration(milliseconds: 29300)` (~30 s). + + --- + + - `isTestMode` + - `bool?` + + Enables test mode. Usually inferred automatically from the publishable key prefix. + + Default: `false`. + + --- + + - `flags` + - `ClerkSdkFlags` + + Advanced feature flags that affect SDK behaviour. Not needed for typical usage. + + Default: `ClerkSdkFlags()`. + + --- + + - `redirectionGenerator` + - `ClerkRedirectUriGenerator?` + + A function (`Uri? Function(BuildContext, clerk.Strategy)`) that returns the redirect URI the SDK should use after an + OAuth / SSO flow completes. Called with the current `BuildContext` and the strategy being used. + + Default: `null`. + + --- + + - `deepLinkStream` + - `Stream?` + + A stream of incoming deep links. The SDK listens to this stream to detect OAuth / SSO callbacks directed back into + the app. + + Default: `null`. + + --- + + - `defaultLaunchMode` + - `LaunchMode` + + The `url_launcher` launch mode used when opening OAuth / SSO URLs. + + Default: `LaunchMode.externalApplication`. + + --- + + - `supportsHardwareSecurityKeys` + - `bool` + + Whether the device supports hardware security keys (passkeys). Set to `false` when running on an iOS simulator, + which does not support the platform API. + + Default: `true`. + + --- + + - `fileCache` + - `ClerkFileCache?` + + Override the cache used to fetch and store remote assets such as avatars and organisation logos. + + Default: A caching implementation backed by the app documents directory. + + --- + + - `localizations` + - `ClerkSdkLocalizationsCollection?` + + A map (`Map`) of IETF language tags to localizations objects. The SDK picks the best + match for the device locale, falling back to `fallbackLocalization`. + + Default: `{'en': ClerkSdkLocalizationsEn()}`. + + --- + + - `fallbackLocalization` + - `ClerkSdkLocalizations?` + + The localization used when no entry in `localizations` matches the device locale. + + Default: `ClerkSdkLocalizationsEn()`. + + --- + + - `grammars` + - `ClerkSdkGrammarCollection?` + + A map (`Map`) of language codes to grammar helpers used for locale-aware string formatting + (e.g. pluralisation). + + Default: `{'en': ClerkSdkGrammarEn()}`. + + --- + + - `fallbackGrammar` + - `ClerkSdkGrammar?` + + The grammar used when no entry in `grammars` matches the device locale. + + Default: `ClerkSdkGrammarEn()`. + + --- + diff --git a/packages/clerk_flutter/doc/getting-started/install-the-sdk.mdx b/packages/clerk_flutter/doc/getting-started/install-the-sdk.mdx new file mode 100644 index 00000000..5610fd9b --- /dev/null +++ b/packages/clerk_flutter/doc/getting-started/install-the-sdk.mdx @@ -0,0 +1,37 @@ +--- +title: Install the SDK +description: Learn how to install and configure the Clerk Flutter SDK in your Flutter app. +sdk: flutter +--- + + + ## Install the packages + + There are two ways to add the Clerk packages to your Flutter app. + + **Using the Flutter CLI (recommended)** + + ```sh + flutter pub add clerk_flutter clerk_auth + ``` + + **Manually via `pubspec.yaml`** + + In your `pubspec.yaml`, add `clerk_flutter` and `clerk_auth` under `dependencies`, then run `flutter pub get`. + + ```yaml + dependencies: + clerk_flutter: ^0.0.14-beta + clerk_auth: ^0.0.14-beta + ``` + + ## Import the packages + + You can now import the Clerk packages into your Flutter app. + + ```dart + import 'package:clerk_flutter/clerk_flutter.dart'; + import 'package:clerk_auth/clerk_auth.dart'; + ``` + + diff --git a/packages/clerk_flutter/doc/getting-started/quickstart.mdx b/packages/clerk_flutter/doc/getting-started/quickstart.mdx new file mode 100644 index 00000000..20d9caa1 --- /dev/null +++ b/packages/clerk_flutter/doc/getting-started/quickstart.mdx @@ -0,0 +1,90 @@ +--- +title: Flutter Quickstart +description: Add authentication and user management to your Flutter app with Clerk. +sdk: flutter +--- + + + + + ## Enable Native API + + + + ## Create a new Flutter app + + 1. If you don't already have a Flutter app, first follow the [Create a new Flutter + app](https://docs.flutter.dev/reference/create-new-app) + guide in the Flutter Docs. This will guide you step-by-step on how to create a Flutter app in your preferred + development environment. + + 1. Add `clerk_flutter` and `clerk_auth` to your Flutter app. + + ```sh + flutter pub add clerk_flutter clerk_auth + ``` + + 1. Install the Clerk packages + + ```sh + flutter pub get + ``` + + 1. The Clerk packages can now be imported into your Flutter app. + + ## Configure the `AndroidManifest.xml` file + + Note: This is only necessary if you want to make your Flutter app run on Android devices. If you will not release + an Android build of your Flutter app then ignore this section. + + In `android/app/src/main/AndroidManifest.xml`, inside the root ` + ` tag, add the following line to enable + internet permission when running your Flutter app on Android. + + ```xml + + + ... + + ``` + + ## Use Clerk's prebuilt widgets + + Clerk provides prebuilt Flutter widgets that handle authentication flows and user management, removing the need + to + build custom widgets. + + `ClerkAuth` is the root control widget that initializes the Clerk authentication system for your Flutter app. It + must be placed at the root of your widget tree (or near it) to provide authentication state to all + descendant widgets. It manages the ClerkAuthState object and makes it available throughout your app via + InheritedWidget. + + To see how to use `ClerkAuth` check out the [clerk_flutter example + app](https://github.com/clerk/clerk-sdk-flutter/blob/main/packages/clerk_flutter/example/lib/main.dart#L110). + + ## Run your application + + Now run your Flutter app. + + ## Create your first user + + Once the app launches successfully, the `ClerkAuth` widget will appear, allowing you to sign up or + sign in to create your first user. + + + diff --git a/packages/clerk_flutter/doc/guides/deploy-to-production.mdx b/packages/clerk_flutter/doc/guides/deploy-to-production.mdx new file mode 100644 index 00000000..808b3fce --- /dev/null +++ b/packages/clerk_flutter/doc/guides/deploy-to-production.mdx @@ -0,0 +1,140 @@ +--- +title: Deploy your Flutter app to production +description: Learn how to deploy your Flutter app to production. +sdk: flutter +--- + +Before you begin: + +1. You will need to have a domain you own. +1. You will need social sign-in (OAuth) credentials for any providers you want to use in production. Each [OAuth provider](/docs/guides/configure/auth-strategies/social-connections/overview) has a dedicated guide on how to set up OAuth credentials for Clerk production apps. +1. You will need your app registered under [**Native Applications**](https://dashboard.clerk.com/~/native-applications) in the Clerk Dashboard. + +## Create your production instance + +1. Navigate to the [Clerk Dashboard](https://dashboard.clerk.com). +1. At the top of the Dashboard, select the **Development** button to reveal the instance selection dropdown. Select **Create production instance**. +1. You will be prompted with a modal to either clone your development instance settings to your production instance or create your production instance with Clerk's default settings. +1. The homepage of the dashboard will show you what is still required to deploy your production instance. + +> [!WARNING] +> For security reasons, [**SSO connections**](https://dashboard.clerk.com/~/user-authentication/sso-connections), [**Integrations**](https://dashboard.clerk.com/~/integrations), and [**Paths**](https://dashboard.clerk.com/~/paths) settings do not copy over. You will need to set these values again. + +## Update your publishable key + +A common mistake when deploying to production is **forgetting to change your publishable key to your production instance's key.** The best way to handle this is to use environment variables or build-time configuration so the correct key is set for each environment. + +Your publishable key is formatted as `pk_test_` in development and `pk_live_` in production. Pass it to `ClerkAuth` during initialization: + +```dart +ClerkAuth( + config: ClerkAuthConfig( + publishableKey: const String.fromEnvironment('CLERK_PUBLISHABLE_KEY'), + ), + child: MaterialApp(...), +) +``` + +Run your Flutter app with the production key: + +```sh +flutter run --dart-define=CLERK_PUBLISHABLE_KEY=pk_live_... +``` + +Or use a configuration file: + +```sh +flutter run --dart-define-from-file=config.production.json +``` + +> [!TIP] +> The SDK automatically infers whether it is in test or production mode from the publishable key prefix, so no additional configuration is required. + +## OAuth credentials + +In development, Clerk provides shared OAuth credentials for most social providers. + +In production, these shared credentials are not secure and you must provide your own. Each [OAuth provider](/docs/guides/configure/auth-strategies/social-connections/overview) has a dedicated guide on how to set up OAuth credentials for Clerk production apps. + +## Deep link redirect URLs + +If your app uses deep links for OAuth or email link callbacks, you must register the redirect URLs for your production app in the Clerk Dashboard. + +1. Navigate to [**Redirect URLs**](https://dashboard.clerk.com/~/redirect-urls) in the Clerk Dashboard. +1. Add your production deep link scheme, for example `myapp://clerk/oauth` and `myapp://clerk/email-link`. + +> [!NOTE] +> Deep link URL schemes are registered per instance. Ensure the schemes configured in your production app's `Info.plist` (iOS) or `AndroidManifest.xml` (Android) match what you have registered in the Dashboard. + +## iOS setup + +### Confirm your iOS app is registered in production + +Ensure your iOS app exists in the production instance under [**Native Applications**](https://dashboard.clerk.com/~/native-applications). You will need your **App ID Prefix** (Team ID) and **Bundle ID**. + +### Update associated domains + +In Xcode, update the **Associated Domains** entitlement to use your production [Frontend API URL](!frontend-api-url). + +Under **Signing & Capabilities**, add or update the entry to: + +``` +webcredentials: +``` + +Replace `` with the value found on the [**Domains**](https://dashboard.clerk.com/~/domains) page in the Clerk Dashboard. + +## Android setup + +### Confirm your Android app is registered in production + +Ensure your Android app exists in the production instance under [**Native Applications**](https://dashboard.clerk.com/~/native-applications). You will need your **Package Name** and **SHA-256 certificate fingerprint** from your production keystore. + +To get the SHA-256 fingerprint from your production keystore, run: + +```sh +keytool -keystore path-to-production-keystore -list -v +``` + +> [!IMPORTANT] +> Your development and production keystores have different SHA-256 fingerprints. Make sure you register the fingerprint from your **production** keystore, not the debug keystore. + +## Deploy certificates + +The Clerk Dashboard home page shows what steps are still required to deploy your production instance. Once you have completed all necessary steps, a **Deploy certificates** button will appear. Selecting this button deploys your production instance. + +## Test on a physical device + +Build a release build using your production keys and verify the authentication flow on a physical device before releasing to users: + +```sh +# iOS β€” build a release archive +flutter build ipa --dart-define=CLERK_PUBLISHABLE_KEY=pk_live_... + +# Android β€” build a signed release APK +flutter build apk --dart-define=CLERK_PUBLISHABLE_KEY=pk_live_... +``` + +## Troubleshooting + +### DNS records not propagating + +Clerk uses a DNS check to validate CNAME records. If the subdomain is reverse proxied behind a service that points to generic hostnames (such as Cloudflare), the DNS check will fail. Set the DNS record for the subdomain to a **DNS only** mode on your host to prevent proxying. + +### Deployment stuck in certificate issuance + +If your instance is stuck during TLS certificate issuance, this may be caused by [CAA DNS records](https://en.wikipedia.org/wiki/DNS_Certification_Authority_Authorization) on your primary domain. Clerk provisions certificates using either [LetsEncrypt](https://letsencrypt.org/) or [Google Trust Services](https://pki.goog/). Ensure your primary domain does not have CAA records that prevent either authority from issuing certificates. + +To check your CAA records, run: + +```sh +dig example.com +short CAA +``` + +If the command returns an empty response, your domain is correctly configured. If it returns any text, remove the restrictive CAA records from your primary domain. + +### Associated domains not working on iOS + +- Ensure the `webcredentials` entry in Xcode uses your **production** Frontend API URL, not the development URL. +- Allow up to 48 hours for DNS changes to propagate. +- Verify the app bundle ID and Team ID registered in the Clerk Dashboard match your Xcode project. diff --git a/packages/clerk_flutter/doc/guides/sign-in-with-apple.flutter.mdx b/packages/clerk_flutter/doc/guides/sign-in-with-apple.flutter.mdx new file mode 100644 index 00000000..5ed062fb --- /dev/null +++ b/packages/clerk_flutter/doc/guides/sign-in-with-apple.flutter.mdx @@ -0,0 +1,117 @@ +--- +title: Add "Sign in with Apple" to your Flutter app +description: Learn how to add native Sign in with Apple to your Clerk Flutter app on iOS. +sdk: flutter +--- + + + +> [!NOTE] +> Native Sign in with Apple is only available on **iOS**. On Android, use the browser-based OAuth flow instead β€” call `ssoSignIn(context, Strategy.oauthApple)` as described in the [authentication flows reference](/docs/sdk-reference/authentication-flows#sign-in-with-oauth). + +This guide explains how to add native Sign in with Apple to your Flutter app using Clerk. The native approach uses Apple's platform SDK to obtain an ID token directly, which is then passed to Clerk for authentication. + +If you only need browser-based OAuth (a WebView flow that doesn't require additional packages), you can call `ssoSignIn(context, Strategy.oauthApple)` directly β€” see the [authentication flows reference](/docs/sdk-reference/authentication-flows#sign-in-with-oauth). The steps below cover the native ID token approach. + + + ## Enable Apple as a social connection + + 1. Navigate to the [Clerk Dashboard](https://dashboard.clerk.com/~/user-authentication/sso-connections). + 1. In the left sidebar, select **User & Authentication**, then select **SSO Connections**. + 1. Select **Add connection** and select **For all users**. + 1. In the **Choose provider** dropdown, select **Apple**. + 1. Toggle on **Enable for sign-up and sign-in** and select **Save**. + + ## Add the Sign in with Apple capability in Xcode + + 1. Open your Flutter project's iOS workspace in Xcode (`ios/Runner.xcworkspace`). + 1. Select the **Runner** target, then open the **Signing & Capabilities** tab. + 1. Select **+ Capability** and add **Sign In with Apple**. + + > [!NOTE] + > Sign in with Apple works on both iOS Simulators and physical devices. However, simulators have limited support β€” physical devices provide full functionality including biometric authentication (Face ID / Touch ID). + + ## Install dependencies + + Add the `sign_in_with_apple` and `uuid` packages to your `pubspec.yaml`: + + ```sh + flutter pub add sign_in_with_apple uuid + ``` + + ## Implement Sign in with Apple + + ### Using Clerk's pre-built UI + + If you're using `ClerkAuthentication` or `ClerkOAuthPanel`, no additional code is required. Once Apple is enabled as a social connection in the Clerk Dashboard, the **Sign in with Apple** button is rendered automatically. + + ```dart + ClerkAuthentication() + ``` + + ### Using a custom flow + + For custom UI, use `sign_in_with_apple` to obtain an ID token and pass it to Clerk using `idTokenSignIn`. + + ```dart + import 'package:clerk_auth/clerk_auth.dart' as clerk; + import 'package:clerk_flutter/clerk_flutter.dart'; + import 'package:sign_in_with_apple/sign_in_with_apple.dart'; + import 'package:uuid/uuid.dart'; + + Future signInWithApple(BuildContext context) async { + final authState = ClerkAuth.of(context); + + // Request Apple ID credentials with a secure nonce + final credential = await SignInWithApple.getAppleIDCredential( + nonce: const Uuid().v4(), + scopes: [ + AppleIDAuthorizationScopes.email, + AppleIDAuthorizationScopes.fullName, + ], + ); + + final token = credential.identityToken; + if (token == null) return; + + // Pass the ID token to Clerk + await authState.idTokenSignIn( + provider: clerk.IdTokenProvider.apple, + token: token, + ); + + // If this is a new user, a sign-up object may be returned with missing fields + if (authState.signUp case clerk.SignUp signUp + when signUp.missingFields.isNotEmpty) { + await authState.attemptSignUp( + firstName: signUp.missing(clerk.Field.firstName) + ? credential.givenName + : null, + lastName: signUp.missing(clerk.Field.lastName) + ? credential.familyName + : null, + ); + } + } + ``` + + > [!IMPORTANT] + > Apple only provides the user's full name (`givenName` and `familyName`) during their **first** app authorisation. On subsequent sign-ins, this data is not returned. Store the name when you first receive it and treat it as optional thereafter. + + +## Hide My Email + +Apple's **Hide My Email** feature lets users share a private relay email address instead of their real one. Clerk supports this out of the box, but you may need to configure [email communication through Apple's private relay](https://developer.apple.com/documentation/sign_in_with_apple/communicating_using_the_private_relay_service) in your Apple Developer account if your app sends transactional emails. diff --git a/packages/clerk_flutter/doc/guides/sign-in-with-google.flutter.mdx b/packages/clerk_flutter/doc/guides/sign-in-with-google.flutter.mdx new file mode 100644 index 00000000..d5b67883 --- /dev/null +++ b/packages/clerk_flutter/doc/guides/sign-in-with-google.flutter.mdx @@ -0,0 +1,204 @@ +--- +title: Add "Sign in with Google" to your Flutter app +description: Learn how to add native Sign in with Google to your Clerk Flutter app on iOS and Android. +sdk: flutter +--- + + + +This guide explains how to add native Sign in with Google to your Flutter app using Clerk. The native approach uses Google's platform SDK to obtain an ID token directly, which is then passed to Clerk for authentication. + +> [!WARNING] +> On **Android**, Google [does not allow sign-in via in-app browsers](https://developers.googleblog.com/en/modernizing-oauth-interactions-in-native-apps-for-better-usability-and-security). The native ID token approach described in this guide is required for Android. On iOS, the in-app WebView OAuth flow (via `ssoSignIn`) is also supported. + +To make the setup process easier, it's recommended to keep two browser tabs open β€” one for the [Clerk Dashboard](https://dashboard.clerk.com/~/user-authentication/sso-connections) and one for the [Google Cloud Console](https://console.cloud.google.com/). + + + ## Enable Google as a social connection + + 1. Navigate to the [Clerk Dashboard](https://dashboard.clerk.com/~/user-authentication/sso-connections). + 1. In the left sidebar, select **User & Authentication**, then select **SSO Connections**. + 1. Select **Add connection** and select **For all users**. + 1. In the **Choose provider** dropdown, select **Google**. + 1. Save the **Authorized Redirect URI** displayed β€” you will need it in the next step. + 1. Toggle on **Enable for sign-up and sign-in**. + + ## Create Google OAuth credentials + + You need three OAuth clients in Google Cloud Console: one for Android, one for iOS, and one Web client. Clerk uses the Web client for server-side token verification β€” **the Web client is required even for native apps**. + + 1. Navigate to the [Google Cloud Console](https://console.cloud.google.com/). + 1. Select an existing project or [create a new one](https://console.cloud.google.com/projectcreate). + 1. In the top-left, select the menu icon (**≑**) and select **APIs & Services**, then **Credentials**. + + ### Create the Android client + + 1. Next to **Credentials**, select **Create Credentials**, then **OAuth client ID**. + 1. For **Application type**, select **Android**. + 1. Complete the required fields: + - **Package name**: Your app's package name (e.g. `com.example.myapp`), found in `android/app/build.gradle`. + - **SHA-1 certificate fingerprint**: Run the following command, replacing the path with your debug or production keystore: + ```sh + keytool -keystore path-to-debug-or-production-keystore -list -v + ``` + > [!IMPORTANT] + > By default, the debug keystore is at `~/.android/debug.keystore`. The keystore password is `android`. You may need to install [OpenJDK](https://openjdk.org/) to run `keytool`. + 1. Select **Create**. + + ### Create the iOS client + + 1. Select **Create Credentials**, then **OAuth client ID**. + 1. For **Application type**, select **iOS**. + 1. Enter your **Bundle ID** (e.g. `com.example.myapp`), found in your Xcode project settings. + 1. Select **Create**. + + ### Create the Web client + + 1. Select **Create Credentials**, then **OAuth client ID**. + 1. For **Application type**, select **Web application**. + 1. Under **Authorized redirect URIs**, paste the **Authorized Redirect URI** you saved from the Clerk Dashboard. + 1. Select **Create**. A modal will display your **Client ID** and **Client Secret** β€” save these securely. + + ## Set the Client ID and Secret in the Clerk Dashboard + + 1. Return to the [Clerk Dashboard SSO connections page](https://dashboard.clerk.com/~/user-authentication/sso-connections). + 1. Select the Google connection you created earlier. + 1. Enter the **Client ID** and **Client Secret** from the Web client you created in the previous step. + 1. Select **Save**. + + ## Install dependencies + + Add the `google_sign_in` and `uuid` packages to your `pubspec.yaml`: + + ```sh + flutter pub add google_sign_in uuid + ``` + + ## Configure your platforms + + ### Android + + No additional `AndroidManifest.xml` changes are required for the `google_sign_in` package beyond the `INTERNET` permission already present in the quickstart. + + ### iOS + + The `google_sign_in` package requires a URL scheme for the OAuth callback. Add the following to your `ios/Runner/Info.plist`, replacing the value with the reversed form of your **iOS Client ID** from Google Cloud Console: + + ```xml + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + + com.googleusercontent.apps.YOUR_IOS_CLIENT_ID + + + + ``` + + ## Implement Sign in with Google + + ### Using Clerk's pre-built UI (iOS only) + + On iOS, if you're using `ClerkAuthentication` or `ClerkOAuthPanel`, the **Sign in with Google** button is rendered automatically once Google is enabled in the Clerk Dashboard. This uses the in-app WebView OAuth flow and does not require the `google_sign_in` package. + + ```dart + ClerkAuthentication() + ``` + + ### Using a custom flow (iOS and Android) + + For custom UI, or for Android where the in-app browser is not permitted, use `google_sign_in` to obtain an ID token natively and pass it to Clerk. + + > [!IMPORTANT] + > The `serverClientId` must be the **Web client ID** from Google Cloud Console, not the Android or iOS client ID. Clerk requires this to verify the token server-side. + + ```dart + import 'package:clerk_auth/clerk_auth.dart' as clerk; + import 'package:clerk_flutter/clerk_flutter.dart'; + import 'package:google_sign_in/google_sign_in.dart'; + import 'package:uuid/uuid.dart'; + + Future signInWithGoogle(BuildContext context) async { + final authState = ClerkAuth.of(context); + + // Reset the Clerk client before initialising Google Sign-In + await authState.resetClient(); + + final google = GoogleSignIn.instance; + await google.initialize( + // Use your Web client ID from Google Cloud Console + serverClientId: 'YOUR_WEB_CLIENT_ID.apps.googleusercontent.com', + nonce: const Uuid().v4(), + ); + + final account = await google.authenticate( + scopeHint: const ['openid', 'email', 'profile'], + ); + + final token = account.authentication.idToken; + if (token == null) return; + + // Pass the ID token to Clerk + await authState.idTokenSignIn( + provider: clerk.IdTokenProvider.google, + token: token, + ); + + // If this is a new user, a sign-up object may be returned with missing fields + if (authState.signUp case clerk.SignUp signUp + when signUp.missingFields.isNotEmpty) { + final nameParts = account.displayName?.split(' ') ?? []; + await authState.attemptSignUp( + firstName: signUp.missing(clerk.Field.firstName) + ? nameParts.firstOrNull + : null, + lastName: signUp.missing(clerk.Field.lastName) + ? (nameParts.length > 1 ? nameParts.last : null) + : null, + ); + } + } + ``` + + To conditionally show the native Google Sign-In button only when the strategy is available in your Clerk environment: + + ```dart + if (authState.env.config.firstFactors.contains(clerk.Strategy.oauthTokenGoogle)) + ElevatedButton( + onPressed: () => signInWithGoogle(context), + child: const Text('Sign in with Google'), + ), + ``` + + +## Troubleshooting + +### `PlatformException: sign_in_failed` on Android + +Ensure the SHA-1 fingerprint registered in Google Cloud Console matches the keystore you are using to sign your build. Debug and release builds use different keystores with different fingerprints β€” both may need to be registered during development. + +### Token verification fails + +Verify that the `serverClientId` passed to `GoogleSignIn.instance.initialize()` is the **Web client ID**, not the Android or iOS client ID. Clerk uses the Web client for server-side verification. diff --git a/packages/clerk_flutter/doc/sdk-reference/authentication-flows.mdx b/packages/clerk_flutter/doc/sdk-reference/authentication-flows.mdx new file mode 100644 index 00000000..6f87a739 --- /dev/null +++ b/packages/clerk_flutter/doc/sdk-reference/authentication-flows.mdx @@ -0,0 +1,410 @@ +--- +title: Authentication flows +description: Learn how to handle sign-in, sign-up, MFA, password reset, and session management with the Clerk Flutter SDK. +sdk: flutter +--- + +The Flutter SDK provides pre-built widgets that handle authentication flows automatically, as well as a lower-level API for building custom UI. + +## Pre-built authentication UI + +The `ClerkAuthentication` widget handles sign-in and sign-up flows, including OAuth, MFA, and password reset, with no additional code required: + +```dart +ClerkAuthentication() +``` + +Place it anywhere in your widget tree when the user is signed out. Wrap your app with `ClerkAuth` to provide the auth state: + +```dart +ClerkAuth( + publishableKey: 'pk_...', + child: ClerkAuthBuilder( + signedInBuilder: (context, auth) => const HomeScreen(), + signedOutBuilder: (context, auth) => const ClerkAuthentication(), + ), +) +``` + +Other pre-built widgets: + +- `ClerkSignInPanel` β€” sign-in form only +- `ClerkSignUpPanel` β€” sign-up form only +- `ClerkOAuthPanel` β€” OAuth provider buttons only +- `ClerkForgottenPasswordPanel` β€” password reset flow + +--- + +For custom UI, access the auth state with `ClerkAuth.of(context)` and use the methods below. + +## Sign in + +The `attemptSignIn` method is progressive β€” call it repeatedly with updated parameters as the user moves through each step of the flow. + +### Sign in with password + +Sign in with an identifier (email, phone, or username) and password: + +```dart +final auth = ClerkAuth.of(context); + +await auth.attemptSignIn( + strategy: Strategy.password, + identifier: 'user@email.com', + password: 'secretpassword', +); +``` + +### Sign in with OTP (email) + +Send an OTP to the user's email, then verify the code: + +```dart +final auth = ClerkAuth.of(context); + +// Send the code +await auth.attemptSignIn( + strategy: Strategy.emailCode, + identifier: 'user@email.com', +); + +// Verify the code +await auth.attemptSignIn( + strategy: Strategy.emailCode, + code: '123456', +); +``` + +### Sign in with OTP (phone) + +Send an OTP to the user's phone number, then verify the code: + +```dart +final auth = ClerkAuth.of(context); + +// Send the code +await auth.attemptSignIn( + strategy: Strategy.phoneCode, + identifier: '+1234567890', +); + +// Verify the code +await auth.attemptSignIn( + strategy: Strategy.phoneCode, + code: '123456', +); +``` + +### Sign in with email link + +Send a magic link to the user's email. The link will be polled automatically: + +```dart +final auth = ClerkAuth.of(context); + +await auth.attemptSignIn( + strategy: Strategy.emailLink, + identifier: 'user@email.com', + redirectUrl: 'yourapp://clerk/callback', +); +``` + +### Sign in with OAuth + +Sign in using an OAuth provider (e.g., Google, GitHub): + +```dart +final auth = ClerkAuth.of(context); + +await auth.ssoSignIn(context, Strategy.oauthGoogle); +``` + +The SDK handles the OAuth redirect flow in-app using a WebView by default. To handle the redirect externally (e.g., via a deep link), configure a `redirectionGenerator` in `ClerkAuthConfig`. + +### Sign in with ID token + +Sign in with an ID token from an identity provider (e.g., Apple): + +```dart +final auth = ClerkAuth.of(context); + +await auth.idTokenSignIn( + provider: IdTokenProvider.apple, + token: credential.identityToken, +); + +// If the user doesn't exist, transfer to sign-up +if (auth.signIn?.isTransferable == true) { + await auth.transfer(); +} +``` + +### Sign in with passkey + +Sign in using a passkey: + +```dart +final auth = ClerkAuth.of(context); + +await auth.attemptSignIn(strategy: Strategy.passkey); +``` + +### Sign in with Enterprise SSO + +Sign in using Enterprise SSO: + +```dart +final auth = ClerkAuth.of(context); + +await auth.ssoSignIn(context, Strategy.enterpriseSSO); +``` + +### Multi-factor authentication + +After the first factor is verified, if MFA is required, call `attemptSignIn` again with the second factor strategy. + +#### MFA with phone code + +```dart +// Send the SMS code +await auth.attemptSignIn(strategy: Strategy.phoneCode); + +// Verify the code +await auth.attemptSignIn( + strategy: Strategy.phoneCode, + code: '123456', +); +``` + +#### MFA with email code + +```dart +// Send the email code +await auth.attemptSignIn(strategy: Strategy.emailCode); + +// Verify the code +await auth.attemptSignIn( + strategy: Strategy.emailCode, + code: '123456', +); +``` + +#### MFA with TOTP (authenticator app) + +```dart +await auth.attemptSignIn( + strategy: Strategy.totp, + code: '123456', +); +``` + +#### MFA with backup code + +```dart +await auth.attemptSignIn( + strategy: Strategy.backupCode, + code: 'backup-code', +); +``` + +#### Resend a code + +```dart +await auth.resendCode(Strategy.phoneCode); +``` + +### Password reset + +#### Password reset with email + +```dart +final auth = ClerkAuth.of(context); + +// Initiate reset and send code +await auth.initiatePasswordReset( + identifier: 'user@email.com', + strategy: Strategy.resetPasswordEmailCode, +); + +// Verify the code and set the new password +await auth.attemptSignIn( + strategy: Strategy.resetPasswordEmailCode, + code: '123456', + password: 'newpassword', +); +``` + +#### Password reset with phone + +```dart +final auth = ClerkAuth.of(context); + +// Initiate reset and send code +await auth.initiatePasswordReset( + identifier: '+1234567890', + strategy: Strategy.resetPasswordPhoneCode, +); + +// Verify the code and set the new password +await auth.attemptSignIn( + strategy: Strategy.resetPasswordPhoneCode, + code: '123456', + password: 'newpassword', +); +``` + +## Sign up + +The `attemptSignUp` method is progressive β€” call it repeatedly with updated parameters until the user is fully signed up. + +### Create a new sign-up + +```dart +final auth = ClerkAuth.of(context); + +await auth.attemptSignUp( + emailAddress: 'newuser@email.com', + password: 'secretpassword', + firstName: 'John', + lastName: 'Doe', + username: 'johndoe', + phoneNumber: '+1234567890', +); +``` + +### Verify email via OTP + +After submitting a sign-up with an email address, verify it with a code: + +```dart +// Send the verification code (triggered automatically by attemptSignUp, +// or call again with the emailCode strategy) +await auth.attemptSignUp(strategy: Strategy.emailCode); + +// Verify the code +await auth.attemptSignUp( + strategy: Strategy.emailCode, + code: '123456', +); +``` + +### Verify phone via OTP + +```dart +// Send the verification code +await auth.attemptSignUp(strategy: Strategy.phoneCode); + +// Verify the code +await auth.attemptSignUp( + strategy: Strategy.phoneCode, + code: '123456', +); +``` + +### Sign up with OAuth + +```dart +final auth = ClerkAuth.of(context); + +await auth.ssoSignUp(context, Strategy.oauthGoogle); +``` + +### Sign up with ID token + +```dart +final auth = ClerkAuth.of(context); + +await auth.idTokenSignUp( + provider: IdTokenProvider.apple, + idToken: credential.identityToken!, + firstName: credential.givenName, + lastName: credential.familyName, +); + +// If the user already exists, transfer to sign-in +if (auth.signUp?.isTransferable == true) { + await auth.transfer(); +} +``` + +### Sign up with Enterprise SSO + +```dart +final auth = ClerkAuth.of(context); + +await auth.ssoSignUp(context, Strategy.enterpriseSSO); +``` + +## Current sign-in/sign-up + +Access the in-progress sign-in or sign-up for multi-step flows. + +### Current sign-in + +```dart +final signIn = ClerkAuth.of(context).signIn; +``` + +### Current sign-up + +```dart +final signUp = ClerkAuth.of(context).signUp; +``` + +## Session management + +### Sessions + +Retrieve all sessions for the current client: + +```dart +final sessions = ClerkAuth.of(context).client.sessions; +``` + +### Get session token + +Retrieve the current session token to authenticate requests to your backend: + +```dart +final auth = ClerkAuth.of(context); +final tokenObj = await auth.sessionToken(); +final token = tokenObj.jwt; +``` + +You can also listen to the `sessionTokenStream` for token renewals: + +```dart +auth.sessionTokenStream.listen((token) { + // Use token.jwt to authenticate requests +}); +``` + +### Set active session + +Switch to a different session: + +```dart +final auth = ClerkAuth.of(context); +await auth.activate(session); +``` + +### Set active organization + +Set the active organization for the current session: + +```dart +final auth = ClerkAuth.of(context); +await auth.setActiveOrganization(organization); +``` + +### Sign out + +```dart +final auth = ClerkAuth.of(context); + +// Sign out from all sessions +await auth.signOut(); + +// Sign out from a specific session +await auth.signOutOf(session); +``` diff --git a/packages/clerk_flutter/doc/sdk-reference/organization-management.mdx b/packages/clerk_flutter/doc/sdk-reference/organization-management.mdx new file mode 100644 index 00000000..116197d2 --- /dev/null +++ b/packages/clerk_flutter/doc/sdk-reference/organization-management.mdx @@ -0,0 +1,173 @@ +--- +title: Organization management +description: Manage Organizations, Memberships, invitations, and domains with the Clerk Flutter SDK. +sdk: flutter +--- + +Use Clerk's Organization resources to manage members, invitations, and Organization settings. Access the auth state with `ClerkAuth.of(context)`. + +## Organization + +`Organization` represents a Clerk Organization. You can access the active organization via `ClerkAuth.of(context).organization`, or from an `OrganizationMembership` via `membership.organization`. + +### Access the current organization + +```dart +final auth = ClerkAuth.of(context); +final organization = auth.organization; // Organization? (null if no active org) +``` + +### Set active organization + +```dart +final auth = ClerkAuth.of(context); + +await auth.setActiveOrganization(organization); +``` + +### Create organization + +```dart +final auth = ClerkAuth.of(context); + +await auth.createOrganization( + name: 'Acme', + slug: 'acme', +); +``` + +To set a logo at creation time, pass a `dart:io` `File`: + +```dart +await auth.createOrganization( + name: 'Acme', + slug: 'acme', + logo: logoFile, +); +``` + +### Update organization + +```dart +final auth = ClerkAuth.of(context); + +await auth.updateOrganization( + organization: organization, + name: 'Acme Corp', +); +``` + +To update the logo, pass a `dart:io` `File`: + +```dart +await auth.updateOrganization( + organization: organization, + logo: logoFile, +); +``` + +### Leave organization + +```dart +final auth = ClerkAuth.of(context); + +await auth.leaveOrganization(organization: organization); +``` + +--- + +## Organization membership + +`OrganizationMembership` represents the current user's membership within an Organization. You can access memberships from `ClerkAuth.of(context).user?.organizationMemberships`. + +### List memberships + +```dart +final auth = ClerkAuth.of(context); +final memberships = auth.user?.organizationMemberships ?? []; +``` + +### Current membership + +```dart +final auth = ClerkAuth.of(context); +final membership = auth.user?.currentOrganization; +final organization = membership?.organization; +``` + +### Check a permission + +Use `membership.hasPermission` to check if the current user has a specific permission within the organization: + +```dart +final membership = auth.user?.currentOrganization; + +if (membership?.hasPermission(Permission.membershipsManage) == true) { + // User can manage members +} +``` + +Predefined permissions: + +| Permission | Key | +|---|---| +| `Permission.profileManage` | `org:sys_profile:manage` | +| `Permission.profileDelete` | `org:sys_profile:delete` | +| `Permission.membershipsRead` | `org:sys_memberships:read` | +| `Permission.membershipsManage` | `org:sys_memberships:manage` | +| `Permission.domainsRead` | `org:sys_domains:read` | +| `Permission.domainsManage` | `org:sys_domains:manage` | + +--- + +## Organization invitations + +`OrganizationInvitation` represents an invitation sent to the current user to join an Organization. + +### List pending invitations + +```dart +final auth = ClerkAuth.of(context); +final invitations = await auth.fetchOrganizationInvitations(); +``` + +### Accept invitation + +```dart +final auth = ClerkAuth.of(context); + +await auth.acceptOrganizationInvitation(invitation); +``` + +--- + +## Organization domains + +`OrganizationDomain` represents a verified email domain for an Organization. + +### List domains + +```dart +final auth = ClerkAuth.of(context); +final domains = await auth.fetchOrganizationDomains(organization: organization); +``` + +### Create domain + +```dart +final auth = ClerkAuth.of(context); + +await auth.createDomain( + organization: organization, + name: 'acme.com', + mode: EnrollmentMode.automaticInvitation, +); +``` + +Available enrollment modes: + +| Mode | Description | +|---|---| +| `EnrollmentMode.automaticSuggestion` | Users with a matching email domain are suggested to join | +| `EnrollmentMode.automaticInvitation` | Users with a matching email domain are automatically invited | +| `EnrollmentMode.manualInvitation` | Users must be manually invited | diff --git a/packages/clerk_flutter/doc/sdk-reference/user-management.mdx b/packages/clerk_flutter/doc/sdk-reference/user-management.mdx new file mode 100644 index 00000000..4997aca2 --- /dev/null +++ b/packages/clerk_flutter/doc/sdk-reference/user-management.mdx @@ -0,0 +1,207 @@ +--- +title: User management +description: Manage the signed-in user with the Clerk Flutter SDK. +sdk: flutter +--- + +Use `ClerkAuthState` methods to manage the current user's account. Access the auth state with `ClerkAuth.of(context)` and the current user with `ClerkAuth.of(context).user`. + +## Access the current user + +```dart +final auth = ClerkAuth.of(context); +final user = auth.user; // User? (null if not signed in) +``` + +--- + +## Update user + +Update the user's profile fields: + +```dart +final auth = ClerkAuth.of(context); + +await auth.updateUser( + username: 'janedoe', + firstName: 'Jane', + lastName: 'Doe', +); +``` + +### Set profile image + +```dart +final auth = ClerkAuth.of(context); + +await auth.updateUserImage(imageFile); // imageFile is a dart:io File +``` + +### Delete profile image + +```dart +final auth = ClerkAuth.of(context); + +await auth.deleteUserImage(); +``` + +--- + +## Email addresses + +### Add email address + +```dart +final auth = ClerkAuth.of(context); + +await auth.addIdentifyingData( + 'jane@example.com', + IdentifierType.emailAddress, +); +``` + +### Verify email address + +After adding an email address, verify it with the code sent to the user: + +```dart +final auth = ClerkAuth.of(context); + +// Get the unverified email from the user +final email = auth.user?.emailAddresses + ?.firstWhere((e) => e.identifier == 'jane@example.com'); + +// Verify the code +await auth.verifyIdentifyingData(email!, '123456'); +``` + +### Remove email address + +```dart +final auth = ClerkAuth.of(context); + +final email = auth.user?.emailAddresses + ?.firstWhere((e) => e.identifier == 'jane@example.com'); + +await auth.deleteIdentifyingData(email!); +``` + +--- + +## Phone numbers + +### Add phone number + +```dart +final auth = ClerkAuth.of(context); + +await auth.addIdentifyingData( + '+15551234567', + IdentifierType.phoneNumber, +); +``` + +### Verify phone number + +After adding a phone number, verify it with the code sent via SMS: + +```dart +final auth = ClerkAuth.of(context); + +final phone = auth.user?.phoneNumbers + ?.firstWhere((p) => p.identifier == '+15551234567'); + +await auth.verifyIdentifyingData(phone!, '123456'); +``` + +### Remove phone number + +```dart +final auth = ClerkAuth.of(context); + +final phone = auth.user?.phoneNumbers + ?.firstWhere((p) => p.identifier == '+15551234567'); + +await auth.deleteIdentifyingData(phone!); +``` + +--- + +## Passkeys + +### Create passkey + +```dart +final auth = ClerkAuth.of(context); + +await auth.createPasskey(); +``` + +### Remove passkey + +```dart +final auth = ClerkAuth.of(context); + +final passkey = auth.user?.passkeys?.first; + +await auth.deleteIdentifyingData(passkey!); +``` + +--- + +## External accounts + +### Remove external account + +```dart +final auth = ClerkAuth.of(context); + +final account = auth.user?.externalAccounts?.first; + +await auth.deleteExternalAccount(account: account!); +``` + +--- + +## Passwords + +### Update password + +```dart +final auth = ClerkAuth.of(context); + +await auth.updateUserPassword( + 'old-password', + 'new-password', +); +``` + +To keep other sessions active after the password change, pass `signOut: false`: + +```dart +await auth.updateUserPassword( + 'old-password', + 'new-password', + signOut: false, +); +``` + +### Delete password + +```dart +final auth = ClerkAuth.of(context); + +await auth.deleteUserPassword('current-password'); +``` + +--- + +## Delete user + +Permanently deletes the current user's account. Only available if your Clerk instance has self-deletion enabled. + +```dart +final auth = ClerkAuth.of(context); + +await auth.deleteUser(); +```