@capgo/capacitor-social-login 8.1.1 → 8.2.0

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
@@ -14,14 +14,14 @@ If you're currently using `@codetrix-studio/capacitor-google-auth`, we recommend
14
14
  ## About
15
15
  All social logins in one plugin
16
16
 
17
- This plugin implement social auth for:
17
+ This plugin implements social auth for:
18
18
  - Google (with credential manager)
19
- - Apple (with 0auth on android)
20
- - Facebook ( with latest SDK)
19
+ - Apple (with OAuth on android)
20
+ - Facebook (with latest SDK)
21
+ - Twitter/X (OAuth 2.0)
22
+ - Generic OAuth2 (supports multiple providers: GitHub, Azure AD, Auth0, Okta, and any OAuth2-compliant server)
21
23
 
22
- We plan in the future to keep adding others social login and make this plugin the all in one solution.
23
-
24
- This plugin is the only one who implement all 3 majors social login on WEB, IOS and Android
24
+ This plugin is the all-in-one solution for social authentication on Web, iOS, and Android.
25
25
 
26
26
  ## Documentation
27
27
 
@@ -343,6 +343,137 @@ When using `mode: 'offline'`, the login response will only contain:
343
343
 
344
344
  Initialize method to create a script tag with Google lib. We cannot know when it's ready so be sure to do it early in web otherwise it will fail.
345
345
 
346
+ ## OAuth2 (Generic)
347
+
348
+ The plugin supports generic OAuth2 authentication, allowing you to integrate with any OAuth2-compliant provider (GitHub, Azure AD, Auth0, Okta, custom servers, etc.). You can configure multiple OAuth2 providers simultaneously.
349
+
350
+ ### Multi-Provider Configuration
351
+
352
+ ```typescript
353
+ await SocialLogin.initialize({
354
+ oauth2: {
355
+ // GitHub OAuth2
356
+ github: {
357
+ appId: 'your-github-client-id',
358
+ authorizationBaseUrl: 'https://github.com/login/oauth/authorize',
359
+ accessTokenEndpoint: 'https://github.com/login/oauth/access_token',
360
+ redirectUrl: 'myapp://oauth/github',
361
+ scope: 'read:user user:email',
362
+ pkceEnabled: true,
363
+ },
364
+ // Azure AD OAuth2
365
+ azure: {
366
+ appId: 'your-azure-client-id',
367
+ authorizationBaseUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
368
+ accessTokenEndpoint: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
369
+ redirectUrl: 'myapp://oauth/azure',
370
+ scope: 'openid profile email',
371
+ pkceEnabled: true,
372
+ resourceUrl: 'https://graph.microsoft.com/v1.0/me',
373
+ },
374
+ // Auth0 OAuth2
375
+ auth0: {
376
+ appId: 'your-auth0-client-id',
377
+ authorizationBaseUrl: 'https://your-tenant.auth0.com/authorize',
378
+ accessTokenEndpoint: 'https://your-tenant.auth0.com/oauth/token',
379
+ redirectUrl: 'myapp://oauth/auth0',
380
+ scope: 'openid profile email offline_access',
381
+ pkceEnabled: true,
382
+ additionalParameters: {
383
+ audience: 'https://your-api.example.com',
384
+ },
385
+ },
386
+ },
387
+ });
388
+ ```
389
+
390
+ ### Login with a Specific Provider
391
+
392
+ ```typescript
393
+ // Login with GitHub
394
+ const githubResult = await SocialLogin.login({
395
+ provider: 'oauth2',
396
+ options: {
397
+ providerId: 'github', // Required: must match key from initialize()
398
+ },
399
+ });
400
+
401
+ // Login with Azure AD
402
+ const azureResult = await SocialLogin.login({
403
+ provider: 'oauth2',
404
+ options: {
405
+ providerId: 'azure',
406
+ scope: 'openid profile email', // Optional: override default scopes
407
+ },
408
+ });
409
+
410
+ console.log('Access Token:', azureResult.result.accessToken?.token);
411
+ console.log('ID Token:', azureResult.result.idToken);
412
+ console.log('User Data:', azureResult.result.resourceData);
413
+ ```
414
+
415
+ ### Check Login Status
416
+
417
+ ```typescript
418
+ const status = await SocialLogin.isLoggedIn({
419
+ provider: 'oauth2',
420
+ providerId: 'github', // Required for OAuth2
421
+ });
422
+ console.log('Is logged in:', status.isLoggedIn);
423
+ ```
424
+
425
+ ### Logout
426
+
427
+ ```typescript
428
+ await SocialLogin.logout({
429
+ provider: 'oauth2',
430
+ providerId: 'github', // Required for OAuth2
431
+ });
432
+ ```
433
+
434
+ ### Refresh Token
435
+
436
+ ```typescript
437
+ await SocialLogin.refresh({
438
+ provider: 'oauth2',
439
+ options: {
440
+ providerId: 'github', // Required for OAuth2
441
+ },
442
+ });
443
+ ```
444
+
445
+ ### OAuth2 Configuration Options
446
+
447
+ | Option | Type | Required | Description |
448
+ |--------|------|----------|-------------|
449
+ | `appId` | string | Yes | OAuth2 Client ID |
450
+ | `authorizationBaseUrl` | string | Yes | Authorization endpoint URL |
451
+ | `accessTokenEndpoint` | string | No* | Token endpoint URL (*Required for code flow) |
452
+ | `redirectUrl` | string | Yes | Callback URL for OAuth redirect |
453
+ | `responseType` | 'code' \| 'token' | No | OAuth flow type (default: 'code') |
454
+ | `pkceEnabled` | boolean | No | Enable PKCE (default: true) |
455
+ | `scope` | string | No | Default scopes to request |
456
+ | `resourceUrl` | string | No | URL to fetch user profile after auth |
457
+ | `additionalParameters` | Record<string, string> | No | Extra params for authorization URL |
458
+ | `additionalResourceHeaders` | Record<string, string> | No | Extra headers for resource request |
459
+ | `logoutUrl` | string | No | URL to open on logout |
460
+ | `logsEnabled` | boolean | No | Enable debug logging (default: false) |
461
+
462
+ ### Platform-Specific Notes
463
+
464
+ **iOS**: Uses `ASWebAuthenticationSession` for secure authentication.
465
+
466
+ **Android**: Uses a WebView-based authentication flow.
467
+
468
+ **Web**: Opens a popup window for OAuth flow.
469
+
470
+ ### Security Recommendations
471
+
472
+ 1. **Always use PKCE** (`pkceEnabled: true`) for public clients
473
+ 2. **Use authorization code flow** (`responseType: 'code'`) instead of implicit flow
474
+ 3. **Store tokens securely** using [@capgo/capacitor-persistent-account](https://github.com/Cap-go/capacitor-persistent-account)
475
+ 4. **Use HTTPS** for all endpoints and redirect URLs in production
476
+
346
477
  ## Troubleshooting
347
478
 
348
479
 
@@ -464,14 +595,14 @@ Initialize the plugin
464
595
  ### login(...)
465
596
 
466
597
  ```typescript
467
- login<T extends "apple" | "google" | "facebook" | "twitter">(options: Extract<LoginOptions, { provider: T; }>) => Promise<{ provider: T; result: ProviderResponseMap[T]; }>
598
+ login<T extends "apple" | "google" | "facebook" | "twitter" | "oauth2">(options: Extract<LoginOptions, { provider: T; }>) => Promise<{ provider: T; result: ProviderResponseMap[T]; }>
468
599
  ```
469
600
 
470
601
  Login with the selected provider
471
602
 
472
- | Param | Type |
473
- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
474
- | **`options`** | <code><a href="#extract">Extract</a>&lt;{ provider: 'facebook'; options: <a href="#facebookloginoptions">FacebookLoginOptions</a>; }, { provider: T; }&gt; \| <a href="#extract">Extract</a>&lt;{ provider: 'google'; options: <a href="#googleloginoptions">GoogleLoginOptions</a>; }, { provider: T; }&gt; \| <a href="#extract">Extract</a>&lt;{ provider: 'apple'; options: <a href="#appleprovideroptions">AppleProviderOptions</a>; }, { provider: T; }&gt; \| <a href="#extract">Extract</a>&lt;{ provider: 'twitter'; options: <a href="#twitterloginoptions">TwitterLoginOptions</a>; }, { provider: T; }&gt;</code> |
603
+ | Param | Type |
604
+ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
605
+ | **`options`** | <code><a href="#extract">Extract</a>&lt;{ provider: 'facebook'; options: <a href="#facebookloginoptions">FacebookLoginOptions</a>; }, { provider: T; }&gt; \| <a href="#extract">Extract</a>&lt;{ provider: 'google'; options: <a href="#googleloginoptions">GoogleLoginOptions</a>; }, { provider: T; }&gt; \| <a href="#extract">Extract</a>&lt;{ provider: 'apple'; options: <a href="#appleprovideroptions">AppleProviderOptions</a>; }, { provider: T; }&gt; \| <a href="#extract">Extract</a>&lt;{ provider: 'twitter'; options: <a href="#twitterloginoptions">TwitterLoginOptions</a>; }, { provider: T; }&gt; \| <a href="#extract">Extract</a>&lt;{ provider: 'oauth2'; options: <a href="#oauth2loginoptions">OAuth2LoginOptions</a>; }, { provider: T; }&gt;</code> |
475
606
 
476
607
  **Returns:** <code>Promise&lt;{ provider: T; result: ProviderResponseMap[T]; }&gt;</code>
477
608
 
@@ -481,14 +612,14 @@ Login with the selected provider
481
612
  ### logout(...)
482
613
 
483
614
  ```typescript
484
- logout(options: { provider: 'apple' | 'google' | 'facebook' | 'twitter'; }) => Promise<void>
615
+ logout(options: { provider: 'apple' | 'google' | 'facebook' | 'twitter' | 'oauth2'; providerId?: string; }) => Promise<void>
485
616
  ```
486
617
 
487
618
  Logout
488
619
 
489
- | Param | Type |
490
- | ------------- | -------------------------------------------------------------------------- |
491
- | **`options`** | <code>{ provider: 'apple' \| 'google' \| 'facebook' \| 'twitter'; }</code> |
620
+ | Param | Type |
621
+ | ------------- | ----------------------------------------------------------------------------------------------------------- |
622
+ | **`options`** | <code>{ provider: 'apple' \| 'google' \| 'facebook' \| 'twitter' \| 'oauth2'; providerId?: string; }</code> |
492
623
 
493
624
  --------------------
494
625
 
@@ -577,12 +708,33 @@ Get the native Capacitor plugin version
577
708
 
578
709
  #### InitializeOptions
579
710
 
580
- | Prop | Type |
581
- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
582
- | **`twitter`** | <code>{ clientId: string; redirectUrl: string; defaultScopes?: string[]; forceLogin?: boolean; audience?: string; }</code> |
583
- | **`facebook`** | <code>{ appId: string; clientToken?: string; locale?: string; }</code> |
584
- | **`google`** | <code>{ iOSClientId?: string; iOSServerClientId?: string; webClientId?: string; mode?: 'online' \| 'offline'; hostedDomain?: string; redirectUrl?: string; }</code> |
585
- | **`apple`** | <code>{ clientId?: string; redirectUrl?: string; useProperTokenExchange?: boolean; useBroadcastChannel?: boolean; }</code> |
711
+ | Prop | Type | Description |
712
+ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
713
+ | **`oauth2`** | <code><a href="#record">Record</a>&lt;string, <a href="#oauth2providerconfig">OAuth2ProviderConfig</a>&gt;</code> | OAuth2 provider configurations. Supports multiple providers by using a <a href="#record">Record</a> with provider IDs as keys. |
714
+ | **`twitter`** | <code>{ clientId: string; redirectUrl: string; defaultScopes?: string[]; forceLogin?: boolean; audience?: string; }</code> | |
715
+ | **`facebook`** | <code>{ appId: string; clientToken?: string; locale?: string; }</code> | |
716
+ | **`google`** | <code>{ iOSClientId?: string; iOSServerClientId?: string; webClientId?: string; mode?: 'online' \| 'offline'; hostedDomain?: string; redirectUrl?: string; }</code> | |
717
+ | **`apple`** | <code>{ clientId?: string; redirectUrl?: string; useProperTokenExchange?: boolean; useBroadcastChannel?: boolean; }</code> | |
718
+
719
+
720
+ #### OAuth2ProviderConfig
721
+
722
+ Configuration for a single OAuth2 provider instance
723
+
724
+ | Prop | Type | Description | Default |
725
+ | ------------------------------- | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- |
726
+ | **`appId`** | <code>string</code> | The OAuth 2.0 client identifier (App ID / Client ID) | |
727
+ | **`authorizationBaseUrl`** | <code>string</code> | The base URL of the authorization endpoint | |
728
+ | **`accessTokenEndpoint`** | <code>string</code> | The URL to exchange the authorization code for tokens Required for authorization code flow | |
729
+ | **`redirectUrl`** | <code>string</code> | Redirect URL that receives the OAuth callback | |
730
+ | **`resourceUrl`** | <code>string</code> | Optional URL to fetch user profile/resource data after authentication The access token will be sent as Bearer token in the Authorization header | |
731
+ | **`responseType`** | <code>'code' \| 'token'</code> | The OAuth response type - 'code': Authorization Code flow (recommended, requires accessTokenEndpoint) - 'token': Implicit flow (less secure, tokens returned directly) | <code>'code'</code> |
732
+ | **`pkceEnabled`** | <code>boolean</code> | Enable PKCE (Proof Key for Code Exchange) Strongly recommended for public clients (mobile/web apps) | <code>true</code> |
733
+ | **`scope`** | <code>string</code> | Default scopes to request during authorization | |
734
+ | **`additionalParameters`** | <code><a href="#record">Record</a>&lt;string, string&gt;</code> | Additional parameters to include in the authorization request | |
735
+ | **`additionalResourceHeaders`** | <code><a href="#record">Record</a>&lt;string, string&gt;</code> | Additional headers to include when fetching the resource URL | |
736
+ | **`logoutUrl`** | <code>string</code> | Custom logout URL for ending the session | |
737
+ | **`logsEnabled`** | <code>boolean</code> | Enable debug logging | <code>false</code> |
586
738
 
587
739
 
588
740
  #### FacebookLoginResponse
@@ -662,6 +814,20 @@ Get the native Capacitor plugin version
662
814
  | **`email`** | <code>string \| null</code> |
663
815
 
664
816
 
817
+ #### OAuth2LoginResponse
818
+
819
+ | Prop | Type | Description |
820
+ | ------------------ | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
821
+ | **`providerId`** | <code>string</code> | The provider ID that was used for this login |
822
+ | **`accessToken`** | <code><a href="#accesstoken">AccessToken</a> \| null</code> | The access token received from the OAuth provider |
823
+ | **`idToken`** | <code>string \| null</code> | The ID token (JWT) if provided by the OAuth server (e.g., OpenID Connect) |
824
+ | **`refreshToken`** | <code>string \| null</code> | The refresh token if provided (requires appropriate scope like offline_access) |
825
+ | **`resourceData`** | <code><a href="#record">Record</a>&lt;string, unknown&gt; \| null</code> | Resource data fetched from resourceUrl if configured Contains the raw JSON response from the resource endpoint |
826
+ | **`scope`** | <code>string[]</code> | The scopes that were granted |
827
+ | **`tokenType`** | <code>string</code> | Token type (usually 'bearer') |
828
+ | **`expiresIn`** | <code>number \| null</code> | Token expiration time in seconds |
829
+
830
+
665
831
  #### FacebookLoginOptions
666
832
 
667
833
  | Prop | Type | Description | Default |
@@ -706,11 +872,24 @@ Get the native Capacitor plugin version
706
872
  | **`forceLogin`** | <code>boolean</code> | Force the consent screen on every attempt, maps to `force_login=true`. |
707
873
 
708
874
 
875
+ #### OAuth2LoginOptions
876
+
877
+ | Prop | Type | Description |
878
+ | -------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
879
+ | **`providerId`** | <code>string</code> | The provider ID as configured in initialize() This is required to identify which OAuth2 provider to use |
880
+ | **`scope`** | <code>string</code> | Override the scopes for this login request If not provided, uses the scopes from initialization |
881
+ | **`state`** | <code>string</code> | Custom state parameter for CSRF protection If not provided, a random value is generated |
882
+ | **`codeVerifier`** | <code>string</code> | Override PKCE code verifier (for testing purposes) If not provided, a secure random verifier is generated |
883
+ | **`redirectUrl`** | <code>string</code> | Override redirect URL for this login request |
884
+ | **`additionalParameters`** | <code><a href="#record">Record</a>&lt;string, string&gt;</code> | Additional parameters to add to the authorization URL |
885
+
886
+
709
887
  #### isLoggedInOptions
710
888
 
711
- | Prop | Type | Description |
712
- | -------------- | ----------------------------------------------------------- | ----------- |
713
- | **`provider`** | <code>'apple' \| 'google' \| 'facebook' \| 'twitter'</code> | Provider |
889
+ | Prop | Type | Description |
890
+ | ---------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------- |
891
+ | **`provider`** | <code>'apple' \| 'google' \| 'facebook' \| 'twitter' \| 'oauth2'</code> | Provider |
892
+ | **`providerId`** | <code>string</code> | Provider ID for OAuth2 providers (required when provider is 'oauth2') |
714
893
 
715
894
 
716
895
  #### AuthorizationCode
@@ -723,9 +902,10 @@ Get the native Capacitor plugin version
723
902
 
724
903
  #### AuthorizationCodeOptions
725
904
 
726
- | Prop | Type | Description |
727
- | -------------- | ----------------------------------------------------------- | ----------- |
728
- | **`provider`** | <code>'apple' \| 'google' \| 'facebook' \| 'twitter'</code> | Provider |
905
+ | Prop | Type | Description |
906
+ | ---------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------- |
907
+ | **`provider`** | <code>'apple' \| 'google' \| 'facebook' \| 'twitter' \| 'oauth2'</code> | Provider |
908
+ | **`providerId`** | <code>string</code> | Provider ID for OAuth2 providers (required when provider is 'oauth2') |
729
909
 
730
910
 
731
911
  #### FacebookGetProfileResponse
@@ -752,9 +932,16 @@ Get the native Capacitor plugin version
752
932
  ### Type Aliases
753
933
 
754
934
 
935
+ #### Record
936
+
937
+ Construct a type with a set of properties K of type T
938
+
939
+ <code>{
755
940
  [P in K]: T;
756
941
  }</code>
942
+
943
+
757
944
  #### ProviderResponseMap
758
945
 
759
- <code>{ facebook: <a href="#facebookloginresponse">FacebookLoginResponse</a>; google: <a href="#googleloginresponse">GoogleLoginResponse</a>; apple: <a href="#appleproviderresponse">AppleProviderResponse</a>; twitter: <a href="#twitterloginresponse">TwitterLoginResponse</a>; }</code>
946
+ <code>{ facebook: <a href="#facebookloginresponse">FacebookLoginResponse</a>; google: <a href="#googleloginresponse">GoogleLoginResponse</a>; apple: <a href="#appleproviderresponse">AppleProviderResponse</a>; twitter: <a href="#twitterloginresponse">TwitterLoginResponse</a>; oauth2: <a href="#oauth2loginresponse">OAuth2LoginResponse</a>; }</code>
760
947
 
761
948
 
762
949
  #### GoogleLoginResponse
@@ -764,7 +951,7 @@ Get the native Capacitor plugin version
764
951
 
765
952
  #### LoginOptions
766
953
 
767
- <code>{ provider: 'facebook'; options: <a href="#facebookloginoptions">FacebookLoginOptions</a>; } | { provider: 'google'; options: <a href="#googleloginoptions">GoogleLoginOptions</a>; } | { provider: 'apple'; options: <a href="#appleprovideroptions">AppleProviderOptions</a>; } | { provider: 'twitter'; options: <a href="#twitterloginoptions">TwitterLoginOptions</a>; }</code>
954
+ <code>{ provider: 'facebook'; options: <a href="#facebookloginoptions">FacebookLoginOptions</a>; } | { provider: 'google'; options: <a href="#googleloginoptions">GoogleLoginOptions</a>; } | { provider: 'apple'; options: <a href="#appleprovideroptions">AppleProviderOptions</a>; } | { provider: 'twitter'; options: <a href="#twitterloginoptions">TwitterLoginOptions</a>; } | { provider: 'oauth2'; options: <a href="#oauth2loginoptions">OAuth2LoginOptions</a>; }</code>
768
955
 
769
956
 
770
957
  #### Extract
@@ -793,13 +980,6 @@ Get the native Capacitor plugin version
793
980
 
794
981
  <code><a href="#record">Record</a>&lt;string, never&gt;</code>
795
982
 
796
-
797
- #### Record
798
-
799
- Construct a type with a set of properties K of type T
800
-
801
- <code>{
802
983
  [P in K]: T;
803
984
  }</code>
804
-
805
985
  </docgen-api>
806
986
 
807
987
 
@@ -13,5 +13,9 @@
13
13
  android:name="ee.forgr.capacitor.social.login.TwitterLoginActivity"
14
14
  android:exported="false"
15
15
  android:theme="@android:style/Theme.DeviceDefault.Light.NoActionBar" />
16
+ <activity
17
+ android:name="ee.forgr.capacitor.social.login.OAuth2LoginActivity"
18
+ android:exported="false"
19
+ android:theme="@android:style/Theme.DeviceDefault.Light.NoActionBar" />
16
20
  </application>
17
21
  </manifest>
@@ -0,0 +1,110 @@
1
+ package ee.forgr.capacitor.social.login;
2
+
3
+ import android.app.Activity;
4
+ import android.content.Intent;
5
+ import android.graphics.Bitmap;
6
+ import android.net.Uri;
7
+ import android.os.Bundle;
8
+ import android.view.ViewGroup;
9
+ import android.webkit.WebResourceRequest;
10
+ import android.webkit.WebSettings;
11
+ import android.webkit.WebView;
12
+ import android.webkit.WebViewClient;
13
+ import androidx.annotation.Nullable;
14
+ import androidx.annotation.RequiresApi;
15
+
16
+ public class OAuth2LoginActivity extends Activity {
17
+
18
+ public static final String EXTRA_AUTH_URL = "authUrl";
19
+ public static final String EXTRA_REDIRECT_URL = "redirectUrl";
20
+
21
+ private String redirectUrl;
22
+
23
+ @Override
24
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
25
+ super.onCreate(savedInstanceState);
26
+ WebView webView = new WebView(this);
27
+ webView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
28
+ WebSettings settings = webView.getSettings();
29
+ settings.setJavaScriptEnabled(true);
30
+ settings.setDomStorageEnabled(true);
31
+ settings.setLoadWithOverviewMode(true);
32
+ settings.setUseWideViewPort(true);
33
+
34
+ redirectUrl = getIntent().getStringExtra(EXTRA_REDIRECT_URL);
35
+ final String authUrl = getIntent().getStringExtra(EXTRA_AUTH_URL);
36
+
37
+ webView.setWebViewClient(
38
+ new WebViewClient() {
39
+ @Override
40
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
41
+ return handleUrl(url);
42
+ }
43
+
44
+ @RequiresApi(21)
45
+ @Override
46
+ public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
47
+ return handleUrl(request.getUrl().toString());
48
+ }
49
+
50
+ @Override
51
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {
52
+ handleUrl(url);
53
+ }
54
+ }
55
+ );
56
+
57
+ setContentView(webView);
58
+
59
+ if (authUrl == null) {
60
+ finishWithError("Missing authorization URL");
61
+ return;
62
+ }
63
+
64
+ webView.loadUrl(authUrl);
65
+ }
66
+
67
+ private boolean handleUrl(String url) {
68
+ if (redirectUrl != null && url.startsWith(redirectUrl)) {
69
+ Uri uri = Uri.parse(url);
70
+ Intent data = new Intent();
71
+
72
+ // Handle authorization code flow (query parameters)
73
+ data.putExtra("code", uri.getQueryParameter("code"));
74
+ data.putExtra("state", uri.getQueryParameter("state"));
75
+ data.putExtra("error", uri.getQueryParameter("error"));
76
+ data.putExtra("error_description", uri.getQueryParameter("error_description"));
77
+
78
+ // Handle implicit flow (fragment parameters)
79
+ String fragment = uri.getFragment();
80
+ if (fragment != null && !fragment.isEmpty()) {
81
+ String[] pairs = fragment.split("&");
82
+ for (String pair : pairs) {
83
+ String[] keyValue = pair.split("=");
84
+ if (keyValue.length == 2) {
85
+ String key = keyValue[0];
86
+ String value = Uri.decode(keyValue[1]);
87
+ data.putExtra(key, value);
88
+ }
89
+ }
90
+ }
91
+
92
+ setResult(Activity.RESULT_OK, data);
93
+ finish();
94
+ return true;
95
+ }
96
+ return false;
97
+ }
98
+
99
+ private void finishWithError(String message) {
100
+ Intent data = new Intent();
101
+ data.putExtra("error", message);
102
+ setResult(Activity.RESULT_CANCELED, data);
103
+ finish();
104
+ }
105
+
106
+ @Override
107
+ public void onBackPressed() {
108
+ finishWithError("User cancelled");
109
+ }
110
+ }