@capgo/capacitor-social-login 8.1.0 → 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
 
@@ -417,6 +548,15 @@ await SocialLogin.login({
417
548
 
418
549
  **Note**: Other apps like Listonic work with Family Link accounts because they use similar configurations. The default settings may be too restrictive for supervised accounts.
419
550
 
551
+ ## Where to store access tokens?
552
+
553
+ You can use the [@capgo/capacitor-persistent-account](https://github.com/Cap-go/capacitor-persistent-account) plugin for this.
554
+
555
+ This plugin stores data in secure locations for native devices.
556
+
557
+ For Android, it will store data in Android's Account Manager, which provides system-level account management.
558
+ For iOS, it will store data in the Keychain, which is Apple's secure credential storage.
559
+
420
560
  ## API
421
561
 
422
562
  <docgen-index>
@@ -455,14 +595,14 @@ Initialize the plugin
455
595
  ### login(...)
456
596
 
457
597
  ```typescript
458
- 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]; }>
459
599
  ```
460
600
 
461
601
  Login with the selected provider
462
602
 
463
- | Param | Type |
464
- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
465
- | **`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> |
466
606
 
467
607
  **Returns:** <code>Promise&lt;{ provider: T; result: ProviderResponseMap[T]; }&gt;</code>
468
608
 
@@ -472,14 +612,14 @@ Login with the selected provider
472
612
  ### logout(...)
473
613
 
474
614
  ```typescript
475
- logout(options: { provider: 'apple' | 'google' | 'facebook' | 'twitter'; }) => Promise<void>
615
+ logout(options: { provider: 'apple' | 'google' | 'facebook' | 'twitter' | 'oauth2'; providerId?: string; }) => Promise<void>
476
616
  ```
477
617
 
478
618
  Logout
479
619
 
480
- | Param | Type |
481
- | ------------- | -------------------------------------------------------------------------- |
482
- | **`options`** | <code>{ provider: 'apple' \| 'google' \| 'facebook' \| 'twitter'; }</code> |
620
+ | Param | Type |
621
+ | ------------- | ----------------------------------------------------------------------------------------------------------- |
622
+ | **`options`** | <code>{ provider: 'apple' \| 'google' \| 'facebook' \| 'twitter' \| 'oauth2'; providerId?: string; }</code> |
483
623
 
484
624
  --------------------
485
625
 
@@ -568,12 +708,33 @@ Get the native Capacitor plugin version
568
708
 
569
709
  #### InitializeOptions
570
710
 
571
- | Prop | Type |
572
- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
573
- | **`twitter`** | <code>{ clientId: string; redirectUrl: string; defaultScopes?: string[]; forceLogin?: boolean; audience?: string; }</code> |
574
- | **`facebook`** | <code>{ appId: string; clientToken?: string; locale?: string; }</code> |
575
- | **`google`** | <code>{ iOSClientId?: string; iOSServerClientId?: string; webClientId?: string; mode?: 'online' \| 'offline'; hostedDomain?: string; redirectUrl?: string; }</code> |
576
- | **`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> |
577
738
 
578
739
 
579
740
  #### FacebookLoginResponse
@@ -653,6 +814,20 @@ Get the native Capacitor plugin version
653
814
  | **`email`** | <code>string \| null</code> |
654
815
 
655
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
+
656
831
  #### FacebookLoginOptions
657
832
 
658
833
  | Prop | Type | Description | Default |
@@ -697,11 +872,24 @@ Get the native Capacitor plugin version
697
872
  | **`forceLogin`** | <code>boolean</code> | Force the consent screen on every attempt, maps to `force_login=true`. |
698
873
 
699
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
+
700
887
  #### isLoggedInOptions
701
888
 
702
- | Prop | Type | Description |
703
- | -------------- | ----------------------------------------------------------- | ----------- |
704
- | **`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') |
705
893
 
706
894
 
707
895
  #### AuthorizationCode
@@ -714,9 +902,10 @@ Get the native Capacitor plugin version
714
902
 
715
903
  #### AuthorizationCodeOptions
716
904
 
717
- | Prop | Type | Description |
718
- | -------------- | ----------------------------------------------------------- | ----------- |
719
- | **`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') |
720
909
 
721
910
 
722
911
  #### FacebookGetProfileResponse
@@ -743,9 +932,16 @@ Get the native Capacitor plugin version
743
932
  ### Type Aliases
744
933
 
745
934
 
935
+ #### Record
936
+
937
+ Construct a type with a set of properties K of type T
938
+
939
+ <code>{
746
940
  [P in K]: T;
747
941
  }</code>
942
+
943
+
748
944
  #### ProviderResponseMap
749
945
 
750
- <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>
751
947
 
752
948
 
753
949
  #### GoogleLoginResponse
@@ -755,7 +951,7 @@ Get the native Capacitor plugin version
755
951
 
756
952
  #### LoginOptions
757
953
 
758
- <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>
759
955
 
760
956
 
761
957
  #### Extract
@@ -784,13 +980,6 @@ Get the native Capacitor plugin version
784
980
 
785
981
  <code><a href="#record">Record</a>&lt;string, never&gt;</code>
786
982
 
787
-
788
- #### Record
789
-
790
- Construct a type with a set of properties K of type T
791
-
792
- <code>{
793
983
  [P in K]: T;
794
984
  }</code>
795
-
796
985
  </docgen-api>
797
986
 
798
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
+ }