@vunexa/lixa 0.0.1-alpha.20 → 0.0.1-alpha.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,14 +33,22 @@ yarn add @vunexa/lixa
33
33
 
34
34
  ## Quick Start
35
35
 
36
- ### 1. Configure lixa with multiple providers
36
+ ### 1. Install packages
37
+
38
+ ```bash
39
+ npm install @vunexa/lixa @vunexa/lixa-providers
40
+ ```
41
+
42
+ ### 2. Configure Lixa with providers
37
43
 
38
44
  ```typescript
39
45
  import { Lixa } from "@vunexa/lixa";
46
+ import { GoogleProvider, GithubProvider } from "@vunexa/lixa-providers";
40
47
 
41
48
  const lixa = new Lixa({
42
49
  providers: {
43
50
  google: {
51
+ provider: new GoogleProvider(),
44
52
  clientId: process.env.GOOGLE_CLIENT_ID!,
45
53
  clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
46
54
  redirectUri: "https://yourapp.com/auth/google/callback",
@@ -51,11 +59,11 @@ const lixa = new Lixa({
51
59
  },
52
60
  },
53
61
  github: {
62
+ provider: new GithubProvider(),
54
63
  clientId: process.env.GITHUB_CLIENT_ID!,
55
64
  clientSecret: process.env.GITHUB_CLIENT_SECRET!,
56
65
  redirectUri: "https://yourapp.com/auth/github/callback",
57
66
  scopes: ["read:user", "user:email"],
58
- extraConfig: {},
59
67
  },
60
68
  },
61
69
 
@@ -72,22 +80,22 @@ const lixa = new Lixa({
72
80
  });
73
81
  ```
74
82
 
75
- ### 2. Redirect users to the provider's authorization URL
83
+ ### 3. Redirect users to the provider's authorization URL
76
84
 
77
85
  ```typescript
78
86
  app.get("/login", (req, res) => {
79
87
  const provider = req.query.provider as string; // 'google' or 'github'
80
- const state = lixa.generateRandomState();
88
+ const state = Lixa.generateRandomState();
81
89
 
82
90
  // Store state in session for validation
83
91
  req.session.oauthState = state;
84
92
 
85
- const authUrl = lixa.getAuthUrl(provider.toUpperCase(), state);
93
+ const authUrl = lixa.getAuthUrl(provider, state);
86
94
  res.redirect(authUrl);
87
95
  });
88
96
  ```
89
97
 
90
- ### 3. Handle the provider callback and establish a session
98
+ ### 4. Handle the provider callback and establish a session
91
99
 
92
100
  ```typescript
93
101
  app.get("/auth/:provider/callback", async (req, res) => {
@@ -100,14 +108,17 @@ app.get("/auth/:provider/callback", async (req, res) => {
100
108
  throw new Error("Invalid state parameter");
101
109
  }
102
110
 
103
- const session = await lixa.handleCallback({
111
+ const sessionId = await lixa.handleCallback({
104
112
  provider,
105
113
  code: code as string,
106
114
  state: state as string,
107
115
  });
108
116
 
109
- // Session established
110
- res.cookie("session_token", session.token, {
117
+ // Session established - retrieve session info if needed
118
+ const session = await lixa.fetchSessionInfo(sessionId);
119
+
120
+ // Store session ID in cookie
121
+ res.cookie("session_id", sessionId, {
111
122
  httpOnly: true,
112
123
  secure: true,
113
124
  sameSite: "strict",
@@ -121,6 +132,118 @@ app.get("/auth/:provider/callback", async (req, res) => {
121
132
  });
122
133
  ```
123
134
 
135
+ ## Provider Configuration
136
+
137
+ ### Using Built-in Providers
138
+
139
+ Built-in providers are available in the `@vunexa/lixa-providers` package:
140
+
141
+ ```typescript
142
+ import { Lixa } from "@vunexa/lixa";
143
+ import { GoogleProvider, GithubProvider } from "@vunexa/lixa-providers";
144
+
145
+ const lixa = new Lixa({
146
+ providers: {
147
+ google: {
148
+ provider: new GoogleProvider(),
149
+ clientId: process.env.GOOGLE_CLIENT_ID!,
150
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
151
+ redirectUri: "https://yourapp.com/auth/google/callback",
152
+ scopes: ["openid", "email", "profile"]
153
+ }
154
+ }
155
+ });
156
+ ```
157
+
158
+ ### Using Custom Inline Providers
159
+
160
+ You can create custom providers by implementing the `IProvider` interface:
161
+
162
+ ```typescript
163
+ import { Lixa, IProvider } from "@vunexa/lixa";
164
+
165
+ // Define your custom provider
166
+ const customProvider: IProvider = {
167
+ authorizationEndpoint: "https://provider.com/oauth/authorize",
168
+ tokenEndpoint: "https://provider.com/oauth/token",
169
+ userInfoEndpoint: "https://provider.com/api/user"
170
+ };
171
+
172
+ // Use it directly in configuration
173
+ const lixa = new Lixa({
174
+ providers: {
175
+ custom: {
176
+ provider: customProvider,
177
+ clientId: "your-client-id",
178
+ clientSecret: "your-client-secret",
179
+ redirectUri: "https://yourapp.com/auth/custom/callback",
180
+ scopes: ["read:user"]
181
+ }
182
+ }
183
+ });
184
+ ```
185
+
186
+ ### Mixing Built-in and Custom Providers
187
+
188
+ You can use both built-in and custom providers in the same configuration:
189
+
190
+ ```typescript
191
+ import { Lixa, IProvider } from "@vunexa/lixa";
192
+ import { GoogleProvider } from "@vunexa/lixa-providers";
193
+
194
+ const customProvider: IProvider = {
195
+ authorizationEndpoint: "https://custom.com/oauth/authorize",
196
+ tokenEndpoint: "https://custom.com/oauth/token",
197
+ userInfoEndpoint: "https://custom.com/api/user"
198
+ };
199
+
200
+ const lixa = new Lixa({
201
+ providers: {
202
+ google: {
203
+ provider: new GoogleProvider(),
204
+ clientId: process.env.GOOGLE_CLIENT_ID!,
205
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
206
+ redirectUri: "https://yourapp.com/auth/google/callback",
207
+ scopes: ["openid", "email", "profile"]
208
+ },
209
+ custom: {
210
+ provider: customProvider,
211
+ clientId: process.env.CUSTOM_CLIENT_ID!,
212
+ clientSecret: process.env.CUSTOM_CLIENT_SECRET!,
213
+ redirectUri: "https://yourapp.com/auth/custom/callback",
214
+ scopes: ["read:user"]
215
+ }
216
+ }
217
+ });
218
+ ```
219
+
220
+ ### Overriding Built-in Providers (Testing)
221
+
222
+ You can override built-in providers with custom implementations for testing:
223
+
224
+ ```typescript
225
+ import { Lixa, IProvider } from "@vunexa/lixa";
226
+
227
+ // Mock provider for testing
228
+ const mockGoogleProvider: IProvider = {
229
+ authorizationEndpoint: "http://localhost:3000/mock/authorize",
230
+ tokenEndpoint: "http://localhost:3000/mock/token",
231
+ userInfoEndpoint: "http://localhost:3000/mock/userinfo"
232
+ };
233
+
234
+ const lixa = new Lixa({
235
+ providers: {
236
+ google: {
237
+ provider: mockGoogleProvider, // Override with mock
238
+ clientId: "test-client-id",
239
+ clientSecret: "test-client-secret",
240
+ redirectUri: "http://localhost:3000/callback",
241
+ scopes: ["openid", "email"]
242
+ }
243
+ }
244
+ });
245
+ ```
246
+
124
247
  ## Advanced Usage
125
248
 
126
249
  ### Custom State Storage (StateDao)
@@ -275,9 +398,11 @@ Debug mode logs:
275
398
  - Session creation
276
399
  - Error details from OAuth providers
277
400
 
278
- ### Custom Provider Registration
401
+ ### Legacy Provider Registration (Deprecated)
402
+
403
+ > **Note**: This pattern is deprecated. Use inline providers instead (see examples above).
279
404
 
280
- You can register custom OAuth providers by implementing the `IProvider` interface:
405
+ For backward compatibility, you can still register providers globally:
281
406
 
282
407
  ```typescript
283
408
  import { Lixa, IProvider } from "@vunexa/lixa";
@@ -293,7 +418,7 @@ Lixa.registerProvider({
293
418
  custom: new CustomProvider(),
294
419
  });
295
420
 
296
- // Use it in your configuration
421
+ // Use it in your configuration (without provider field)
297
422
  const lixa = new Lixa({
298
423
  providers: {
299
424
  custom: {
@@ -306,17 +431,17 @@ const lixa = new Lixa({
306
431
  });
307
432
  ```
308
433
 
309
- ### Check Provider Registration
434
+ ### Check Provider Configuration
310
435
 
311
436
  ```typescript
312
- // Check if a provider is registered
313
- if (Lixa.isProviderRegistered("google")) {
314
- console.log("Google provider is available");
437
+ // Check if a provider is configured for this instance
438
+ if (lixa.isProviderConfigured("google")) {
439
+ console.log("Google provider is configured");
315
440
  }
316
441
 
317
- // Get list of all registered providers
442
+ // Get list of all registered providers (legacy)
318
443
  const providers = Lixa.getRegisteredProviders();
319
- console.log("Available providers:", providers);
444
+ console.log("Registered providers:", providers);
320
445
  ```
321
446
 
322
447
  ## Common Pitfalls & Solutions
@@ -395,34 +520,38 @@ createSession: async (tokenData) => {
395
520
 
396
521
  #### Static Methods
397
522
 
398
- - `Lixa.registerProvider(providerMap: { [key: string]: IProvider })` - Register custom providers before creating instances
399
- - `Lixa.isProviderRegistered(provider: string): boolean` - Check if a provider is registered
400
- - `Lixa.getRegisteredProviders(): string[]` - Get list of all registered provider names
401
- - `Lixa.generateRandomState(): string` - Generate a cryptographically secure random state string
523
+ - `Lixa.generateRandomState(): string` - Generate a cryptographically secure random state string for CSRF protection
524
+ - `Lixa.registerProvider(providerMap: { [key: string]: IProvider })` - **[Deprecated]** Register custom providers globally. Use inline providers instead.
525
+ - `Lixa.getRegisteredProviders(): string[]` - **[Deprecated]** Get list of all registered provider names from legacy registry
402
526
 
403
527
  #### Instance Methods
404
528
 
405
- - `getAuthUrl(provider: string, state: string): string` - Generate authorization URL for a provider. Automatically stores state with PKCE code verifier.
406
- - `handleCallback({ provider, code, state }): Promise<string>` - Handle OAuth callback, validate state, exchange code for tokens, and create session. Returns session ID.
407
- - `fetchSessionInfo(sessionId: string): Promise<Session | null>` - Retrieve session information by session ID
529
+ - `getAuthUrl(provider: string, state: string): string` - Generate authorization URL for a provider. Automatically generates PKCE code verifier and challenge, stores state with code verifier for later validation.
530
+ - `handleCallback({ provider, code, state }): Promise<string>` - Handle OAuth callback, validate state parameter, retrieve PKCE code verifier, exchange authorization code for tokens, and create session using SessionStrategy. Returns session ID.
531
+ - `fetchSessionInfo(sessionId: string): Promise<Session | null>` - Retrieve session information by session ID from SessionDao.
532
+ - `isProviderConfigured(provider: string): boolean` - Check if a provider is configured for this Lixa instance. Useful for type guards and runtime validation.
408
533
 
409
534
  ### Types
410
535
 
411
536
  ```typescript
412
- interface ProviderConfig {
537
+ // Provider configuration with inline provider support
538
+ type ProviderConfig = {
413
539
  clientId: string;
414
540
  clientSecret: string;
415
541
  redirectUri: string;
416
542
  scopes: string[];
417
- extraConfig?: Record<string, any>; // Provider-specific parameters
418
- }
419
-
420
- interface LixaConfig {
421
- providers: Record<string, ProviderConfig>;
543
+ extraConfig?: Record<string, any>;
544
+ } & (
545
+ | { provider?: never } // Built-in provider (no provider field)
546
+ | { provider: IProvider } // Custom provider (provider field required)
547
+ );
548
+
549
+ interface LixaConfig<TProviders = Record<string, ProviderConfig>> {
550
+ providers: TProviders;
422
551
  sessionStrategy?: SessionStrategy;
423
- stateDao?: StateDao; // Custom state storage
424
- sessionDao?: SessionDao; // Custom session storage
425
- debug?: boolean; // Enable debug logging
552
+ stateDao?: StateDao;
553
+ sessionDao?: SessionDao;
554
+ debug?: boolean;
426
555
  }
427
556
 
428
557
  interface SessionStrategy {
@@ -430,19 +559,25 @@ interface SessionStrategy {
430
559
  }
431
560
 
432
561
  interface Session {
433
- token: string; // Your custom session identifier
434
- raw: any; // Raw data you want to store with the session
562
+ token: string;
563
+ raw: any;
564
+ }
565
+
566
+ interface StateData {
567
+ provider: string; // Provider name for routing
568
+ codeVerifier: string; // PKCE code verifier (64 hex chars)
569
+ createdAt: number; // Unix timestamp in milliseconds
435
570
  }
436
571
 
437
572
  interface StateDao {
438
- saveState(state: string, data: any, expiresInSeconds: number): Promise<void>;
439
- getState(state: string): Promise<any | null>;
573
+ saveState(state: string, data: StateData, expiresInSeconds: number): Promise<void>;
574
+ getState(state: string): Promise<StateData | null>;
440
575
  deleteState(state: string): Promise<void>;
441
576
  }
442
577
 
443
578
  interface SessionDao {
444
- saveSession(sessionId: string, session: Session, expiresInSeconds: number): Promise<void>;
445
- getSession(sessionId: string): Promise<Session | null>;
579
+ saveSession(sessionId: string, session: any, expiresInSeconds: number): Promise<void>;
580
+ getSession(sessionId: string): Promise<any | null>;
446
581
  deleteSession(sessionId: string): Promise<void>;
447
582
  }
448
583
 
@@ -455,21 +590,292 @@ interface IProvider {
455
590
 
456
591
  ## Built-in Providers
457
592
 
458
- ### Google
593
+ Built-in providers are available in the `@vunexa/lixa-providers` package:
594
+
595
+ ```bash
596
+ npm install @vunexa/lixa-providers
597
+ ```
598
+
599
+ ### Google (`GoogleProvider`)
600
+
601
+ ```typescript
602
+ import { GoogleProvider } from "@vunexa/lixa-providers";
603
+ ```
604
+
459
605
  - **Type**: OAuth 2.0 + OpenID Connect (OIDC)
460
606
  - **Authorization Endpoint**: `https://accounts.google.com/o/oauth2/v2/auth`
461
607
  - **Token Endpoint**: `https://oauth2.googleapis.com/token`
462
608
  - **User Info Endpoint**: `https://www.googleapis.com/oauth2/v2/userinfo`
463
609
  - **Supports**: PKCE, ID tokens, refresh tokens
464
- - **Common Scopes**: `openid`, `email`, `profile`
610
+ - **Common Scopes**:
611
+ - `openid` - Required for OpenID Connect
612
+ - `email` - Access to user's email address
613
+ - `profile` - Access to user's basic profile information
614
+ - `https://www.googleapis.com/auth/drive.readonly` - Read-only access to Google Drive
615
+ - `https://www.googleapis.com/auth/calendar.readonly` - Read-only access to Google Calendar
616
+
617
+ [Full list of Google OAuth scopes](https://developers.google.com/identity/protocols/oauth2/scopes)
618
+
619
+ ### GitHub (`GithubProvider`)
620
+
621
+ ```typescript
622
+ import { GithubProvider } from "@vunexa/lixa-providers";
623
+ ```
465
624
 
466
- ### GitHub
467
625
  - **Type**: OAuth 2.0
468
626
  - **Authorization Endpoint**: `https://github.com/login/oauth/authorize`
469
627
  - **Token Endpoint**: `https://github.com/login/oauth/access_token`
470
628
  - **User Info Endpoint**: `https://api.github.com/user`
471
629
  - **Supports**: PKCE
472
- - **Common Scopes**: `read:user`, `user:email`
630
+ - **Common Scopes**:
631
+ - `user` - Read/write access to profile info
632
+ - `user:email` - Read access to user's email addresses
633
+ - `read:user` - Read-only access to profile info
634
+ - `repo` - Full control of private repositories
635
+ - `public_repo` - Access to public repositories
636
+ - `gist` - Create gists
637
+ - `read:org` - Read-only access to organization membership
638
+
639
+ [Full list of GitHub OAuth scopes](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps)
640
+
641
+ ## Extensibility Interfaces
642
+
643
+ ### SessionStrategy Interface
644
+
645
+ The `SessionStrategy` interface allows you to customize how OAuth tokens are converted into application sessions:
646
+
647
+ ```typescript
648
+ interface SessionStrategy {
649
+ createSession(tokenData: any): Promise<Session>;
650
+ }
651
+
652
+ interface Session {
653
+ token: string; // Your session identifier
654
+ raw: any; // Additional session data
655
+ }
656
+ ```
657
+
658
+ **Use cases:**
659
+ - Decode ID tokens for user information
660
+ - Create or update users in your database
661
+ - Generate custom session identifiers
662
+ - Store tokens securely
663
+ - Add custom claims or metadata
664
+
665
+ **Example with database integration:**
666
+ ```typescript
667
+ const dbSessionStrategy: SessionStrategy = {
668
+ createSession: async (tokenData) => {
669
+ // Decode ID token (for OIDC providers)
670
+ const payload = decodeJwt(tokenData.id_token);
671
+
672
+ // Create/update user
673
+ const user = await db.users.upsert({
674
+ email: payload.email,
675
+ name: payload.name
676
+ });
677
+
678
+ // Generate session
679
+ const sessionId = generateId();
680
+ await db.sessions.create({
681
+ id: sessionId,
682
+ userId: user.id,
683
+ accessToken: tokenData.access_token,
684
+ refreshToken: tokenData.refresh_token
685
+ });
686
+
687
+ return {
688
+ token: sessionId,
689
+ raw: { userId: user.id, ...tokenData }
690
+ };
691
+ }
692
+ };
693
+ ```
694
+
695
+ ### StateDao Interface
696
+
697
+ The `StateDao` interface manages OAuth state storage for CSRF protection and PKCE:
698
+
699
+ ```typescript
700
+ interface StateDao {
701
+ saveState(state: string, data: StateData, expiresInSeconds: number): Promise<void>;
702
+ getState(state: string): Promise<StateData | null>;
703
+ deleteState(state: string): Promise<void>;
704
+ }
705
+
706
+ interface StateData {
707
+ provider: string; // Provider name for routing
708
+ codeVerifier: string; // PKCE code verifier (64 hex chars)
709
+ createdAt: number; // Unix timestamp in milliseconds
710
+ }
711
+ ```
712
+
713
+ **Required fields:**
714
+ - `provider`: Used to route callbacks to the correct provider configuration
715
+ - `codeVerifier`: Required for PKCE token exchange (RFC 7636)
716
+ - `createdAt`: Timestamp for debugging and validation
717
+
718
+ **Example with Redis:**
719
+ ```typescript
720
+ const redisStateDao: StateDao = {
721
+ saveState: async (state, data, expiresInSeconds) => {
722
+ await redis.setex(
723
+ `oauth:state:${state}`,
724
+ expiresInSeconds,
725
+ JSON.stringify(data)
726
+ );
727
+ },
728
+
729
+ getState: async (state) => {
730
+ const data = await redis.get(`oauth:state:${state}`);
731
+ return data ? JSON.parse(data) : null;
732
+ },
733
+
734
+ deleteState: async (state) => {
735
+ await redis.del(`oauth:state:${state}`);
736
+ }
737
+ };
738
+ ```
739
+
740
+ ### SessionDao Interface
741
+
742
+ The `SessionDao` interface manages persistent user session storage:
743
+
744
+ ```typescript
745
+ interface SessionDao {
746
+ saveSession(sessionId: string, data: any, expiresInSeconds: number): Promise<void>;
747
+ getSession(sessionId: string): Promise<any | null>;
748
+ deleteSession(sessionId: string): Promise<void>;
749
+ }
750
+ ```
751
+
752
+ **Example with database:**
753
+ ```typescript
754
+ const dbSessionDao: SessionDao = {
755
+ saveSession: async (sessionId, data, expiresInSeconds) => {
756
+ const expiresAt = new Date(Date.now() + expiresInSeconds * 1000);
757
+ await db.sessions.create({
758
+ id: sessionId,
759
+ data: JSON.stringify(data),
760
+ expiresAt
761
+ });
762
+ },
763
+
764
+ getSession: async (sessionId) => {
765
+ const session = await db.sessions.findOne({
766
+ id: sessionId,
767
+ expiresAt: { $gt: new Date() }
768
+ });
769
+ return session ? JSON.parse(session.data) : null;
770
+ },
771
+
772
+ deleteSession: async (sessionId) => {
773
+ await db.sessions.delete({ id: sessionId });
774
+ }
775
+ };
776
+ ```
777
+
778
+ ## PKCE Implementation
779
+
780
+ Lixa automatically implements **PKCE (Proof Key for Code Exchange)** according to [RFC 7636](https://tools.ietf.org/html/rfc7636) for all OAuth flows.
781
+
782
+ ### How it Works
783
+
784
+ 1. **Code Verifier Generation**
785
+ - Generates 32 cryptographically random bytes
786
+ - Encodes as 64-character hexadecimal string
787
+ - Example: `a1b2c3d4e5f6...` (64 chars)
788
+
789
+ 2. **Code Challenge Generation**
790
+ - Creates SHA-256 hash of the code verifier
791
+ - Encodes as base64url (RFC 4648)
792
+ - Sends with authorization request
793
+
794
+ 3. **Token Exchange**
795
+ - Retrieves code verifier from state storage
796
+ - Sends original code verifier with token request
797
+ - Provider validates: `SHA256(code_verifier) === code_challenge`
798
+
799
+ ### Security Benefits
800
+
801
+ - **Prevents authorization code interception**: Even if an attacker intercepts the authorization code, they cannot exchange it for tokens without the code verifier
802
+ - **No client secret required**: PKCE works without client secrets, making it suitable for public clients
803
+ - **Standards compliant**: Follows RFC 7636 specification
804
+
805
+ ### Debug Logging
806
+
807
+ Enable debug mode to see PKCE flow details:
808
+
809
+ ```typescript
810
+ const lixa = new Lixa({
811
+ providers: { /* ... */ },
812
+ debug: true
813
+ });
814
+ ```
815
+
816
+ Debug output includes:
817
+ - `[Lixa] [timestamp] [INFO] [Auth]` - Code verifier and challenge generation
818
+ - `[Lixa] [timestamp] [INFO] [State]` - State storage with code verifier
819
+ - `[Lixa] [timestamp] [INFO] [Token]` - Token exchange with code verifier
820
+
821
+ ## Migration Guide
822
+
823
+ ### Migrating from Legacy Registration Pattern
824
+
825
+ **Old Pattern (Deprecated):**
826
+ ```typescript
827
+ import { Lixa, IProvider } from "@vunexa/lixa";
828
+ import { GoogleProvider } from "@vunexa/lixa";
829
+
830
+ // Step 1: Register providers globally
831
+ Lixa.registerProvider({
832
+ google: new GoogleProvider()
833
+ });
834
+
835
+ // Step 2: Configure with credentials only
836
+ const lixa = new Lixa({
837
+ providers: {
838
+ google: {
839
+ clientId: "...",
840
+ clientSecret: "...",
841
+ redirectUri: "...",
842
+ scopes: ["openid", "email"]
843
+ }
844
+ }
845
+ });
846
+ ```
847
+
848
+ **New Pattern (Recommended):**
849
+ ```typescript
850
+ import { Lixa } from "@vunexa/lixa";
851
+ import { GoogleProvider } from "@vunexa/lixa-providers";
852
+
853
+ // Single step: Pass provider inline
854
+ const lixa = new Lixa({
855
+ providers: {
856
+ google: {
857
+ provider: new GoogleProvider(), // ← Add this line
858
+ clientId: "...",
859
+ clientSecret: "...",
860
+ redirectUri: "...",
861
+ scopes: ["openid", "email"]
862
+ }
863
+ }
864
+ });
865
+ ```
866
+
867
+ **Migration Steps:**
868
+ 1. Install `@vunexa/lixa-providers`: `npm install @vunexa/lixa-providers`
869
+ 2. Update imports: `import { GoogleProvider } from "@vunexa/lixa-providers"`
870
+ 3. Remove `Lixa.registerProvider()` calls
871
+ 4. Add `provider` field to each provider configuration
872
+ 5. Test your OAuth flows
873
+
874
+ **Benefits:**
875
+ - ✅ No global state - each Lixa instance is independent
876
+ - ✅ Better testability - easy to mock providers per instance
877
+ - ✅ Clearer configuration - everything in one place
878
+ - ✅ Type safety - TypeScript knows which providers are configured
473
879
 
474
880
  ## Security Considerations
475
881