capacitor-oidc 0.0.6 → 0.1.1

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.
Files changed (37) hide show
  1. package/README.md +32 -25
  2. package/SECURITY.md +27 -30
  3. package/dist/esm/base-capacitor-user-manager.d.ts +30 -0
  4. package/dist/esm/base-capacitor-user-manager.js +104 -0
  5. package/dist/esm/base-capacitor-user-manager.js.map +1 -0
  6. package/dist/esm/capacitor-user-manager.d.ts +9 -32
  7. package/dist/esm/capacitor-user-manager.js +20 -174
  8. package/dist/esm/capacitor-user-manager.js.map +1 -1
  9. package/dist/esm/configuration.d.ts +14 -0
  10. package/dist/esm/configuration.js +72 -0
  11. package/dist/esm/configuration.js.map +1 -0
  12. package/dist/esm/definitions.d.ts +53 -3
  13. package/dist/esm/definitions.js.map +1 -1
  14. package/dist/esm/index.d.ts +5 -4
  15. package/dist/esm/index.js +8 -2
  16. package/dist/esm/index.js.map +1 -1
  17. package/dist/esm/native-capacitor-user-manager.d.ts +24 -0
  18. package/dist/esm/native-capacitor-user-manager.js +112 -0
  19. package/dist/esm/native-capacitor-user-manager.js.map +1 -0
  20. package/dist/esm/web-capacitor-user-manager.d.ts +11 -0
  21. package/dist/esm/web-capacitor-user-manager.js +44 -0
  22. package/dist/esm/web-capacitor-user-manager.js.map +1 -0
  23. package/dist/plugin.cjs +303 -126
  24. package/dist/plugin.cjs.map +1 -1
  25. package/docs/API.md +106 -60
  26. package/docs/GETTING_STARTED.md +85 -62
  27. package/docs/PLATFORM_SETUP.md +15 -4
  28. package/docs/PROVIDERS.md +52 -45
  29. package/docs/README.md +4 -4
  30. package/docs/TESTING.md +3 -1
  31. package/docs/TROUBLESHOOTING.md +23 -13
  32. package/docs/adr/0004-use-one-manager-across-platforms.md +64 -0
  33. package/docs/adr/index.md +6 -0
  34. package/docs/frameworks/ANGULAR.md +129 -0
  35. package/docs/frameworks/REACT.md +134 -0
  36. package/docs/frameworks/VUE.md +118 -0
  37. package/package.json +8 -2
@@ -15,6 +15,12 @@ The authorization request's `redirect_uri` must exactly match a callback
15
15
  registered for the provider client. Check scheme, host, port, path, case, and
16
16
  trailing slashes. Wildcard callback hosts are commonly unsupported.
17
17
 
18
+ ## Web login returns without a user
19
+
20
+ Make the configured web callback route call `signinCallback()`. A logout callback
21
+ route must call `signoutCallback()` instead. Both routes must use exactly the URI
22
+ registered with the provider.
23
+
18
24
  ## Android does not return to the app
19
25
 
20
26
  - Keep `MainActivity` in `singleTask` launch mode.
@@ -33,9 +39,10 @@ and that the callback scheme is registered in the target.
33
39
 
34
40
  ## Token, UserInfo, refresh, or revocation fails with a network error
35
41
 
36
- These requests use the WebView's normal `fetch`. Configure the provider endpoint
37
- to allow the app's Capacitor origin through CORS. Static discovery metadata does
38
- not remove CORS requirements from token, UserInfo, refresh, or revocation calls.
42
+ These requests use the runtime's normal `fetch`. Configure the provider endpoint
43
+ to allow both web application and Capacitor origins through CORS. Static discovery
44
+ metadata does not remove CORS requirements from token, UserInfo, refresh, or
45
+ revocation calls.
39
46
 
40
47
  ## No refresh token is available
41
48
 
@@ -43,22 +50,25 @@ not remove CORS requirements from token, UserInfo, refresh, or revocation calls.
43
50
  - Enable the refresh-token grant for the public client.
44
51
  - Check provider-specific consent and rotation settings.
45
52
 
46
- When the current user is expired and has no refresh token, `getValidUser()`
47
- removes the local user and returns `null`.
53
+ On native platforms, an expired user without a refresh token is removed and
54
+ `getValidUser()` returns `null`. Web behavior follows the configured upstream
55
+ silent-renew settings and may use an iframe.
48
56
 
49
57
  ## Startup renewal fails
50
58
 
51
- `CapacitorUserManager.create()` restores secure local state but does not wait for
52
- token renewal. When `automaticSilentRenew` is enabled, a required startup renewal
53
- runs asynchronously and reports failures through `silentRenewError`. Temporary
54
- token-endpoint failures preserve the stored session; a terminal `invalid_grant`
55
- removes it.
59
+ On native platforms, `CapacitorUserManager.create()` restores secure local state
60
+ but does not wait for token renewal. When `automaticSilentRenew` is enabled, a
61
+ required startup renewal runs asynchronously and reports failures through
62
+ `silentRenewError`. Temporary token-endpoint failures preserve the stored
63
+ session; a terminal `invalid_grant` removes it. Web automatic renewal follows
64
+ `oidc-client-ts` behavior.
56
65
 
57
66
  ## Logout does not return to the app
58
67
 
59
- Register the post-logout URI at the provider and in the native application. Some
60
- providers use non-standard parameters. Amazon Cognito uses `logout_uri`; see
61
- [Provider configuration](PROVIDERS.md#amazon-cognito).
68
+ Register the post-logout URI at the provider, add the browser callback route, and
69
+ register native schemes in each native application. Some providers use
70
+ non-standard parameters. Amazon Cognito uses `logout_uri`; see [Provider
71
+ configuration](PROVIDERS.md#amazon-cognito).
62
72
 
63
73
  ## `USER_CANCELLED` appears but Android UI remains visible
64
74
 
@@ -0,0 +1,64 @@
1
+ # ADR 0004: Use one manager across platforms
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ The first package boundary was intentionally native-only. It kept the custom
10
+ code small by adapting `oidc-client-ts` only where Capacitor needed native system
11
+ authentication UI and secure storage. Browser methods were rejected, and the
12
+ documentation told applications to instantiate `oidc-client-ts` directly for
13
+ web.
14
+
15
+ That boundary moved platform selection into every consuming application. A
16
+ cross-platform app had to duplicate common settings, create two manager types,
17
+ branch for sign-in, callbacks, refresh, and logout, and depend directly on the
18
+ underlying library. The package abstracted native mechanics but did not provide
19
+ the originally intended application-level abstraction.
20
+
21
+ ## Decision
22
+
23
+ `CapacitorUserManager` is the public manager for web, iOS, and Android.
24
+ Configuration is split into:
25
+
26
+ - required `common` public-client settings;
27
+ - optional `web` settings and redirect-or-popup interaction choices;
28
+ - optional `native` settings and native options;
29
+ - optional `ios` and `android` overrides of the native section.
30
+
31
+ Resolution is shallow and happens once:
32
+
33
+ ```text
34
+ web: common -> web
35
+ iOS: common -> native -> ios
36
+ Android: common -> native -> android
37
+ ```
38
+
39
+ Web uses the upstream browser navigators and configured browser stores. Native
40
+ uses `CapacitorNavigator`, secure stores, refresh-token-only renewal, and the app
41
+ resume listener. `signin()` and `signout()` return `Promise<void>` on all
42
+ platforms; applications read session state through events or user accessors.
43
+
44
+ The public manager is a common base and factory backed by internal web and
45
+ native implementations. Legacy native input is normalized before implementation
46
+ selection, so both factory signatures use the same native behavior.
47
+
48
+ All platforms remain public clients using code flow with PKCE. Web-only
49
+ capabilities stay in the web settings. Native does not accept custom stores,
50
+ DPoP, iframe callbacks, or browser session monitoring.
51
+
52
+ ## Consequences
53
+
54
+ - Applications no longer branch between two manager implementations or import
55
+ `oidc-client-ts` directly for normal use.
56
+ - Web callbacks remain explicit application routes through `signinCallback()`
57
+ and `signoutCallback()`.
58
+ - Platform-specific provider values and interaction arguments can differ without
59
+ duplicating common configuration.
60
+ - The pre-1.0 native factory signature is normalized into the new implementation
61
+ as a deprecated migration path. The `Promise<void>` sign-in contract remains a
62
+ return-type change for applications that previously used the returned user.
63
+ - The package still owns no OIDC protocol implementation or provider-specific
64
+ behavior.
@@ -0,0 +1,6 @@
1
+ # Architecture decisions
2
+
3
+ - [Use `oidc-client-ts`](0001-use-oidc-client-ts.md)
4
+ - [Keep the native boundary small](0002-keep-the-native-boundary-small.md)
5
+ - [Share session storage with widgets](0003-share-session-storage-with-widgets.md)
6
+ - [Use one manager across platforms](0004-use-one-manager-across-platforms.md)
@@ -0,0 +1,129 @@
1
+ # Angular integration
2
+
3
+ The [Angular example](/examples/angular/) keeps one OIDC manager in a root
4
+ service. The service completes browser callbacks, restores the current user,
5
+ and exposes signals for templates.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm install capacitor-oidc @capacitor/app
11
+ npx cap sync
12
+ ```
13
+
14
+ ## Configure the provider
15
+
16
+ Keep deploy-specific values in an Angular environment file:
17
+
18
+ ```ts
19
+ // src/environments/environment.ts
20
+ export const environment = {
21
+ authority: 'https://identity.example.com',
22
+ clientId: 'public-app',
23
+ };
24
+ ```
25
+
26
+ ## Create a root service
27
+
28
+ `providedIn: 'root'` gives the application one manager, independent of route
29
+ and component lifetimes:
30
+
31
+ ```ts
32
+ @Injectable({ providedIn: 'root' })
33
+ export class OidcService {
34
+ readonly user = signal<User | null>(null);
35
+
36
+ private readonly manager = CapacitorUserManager.create({
37
+ common: {
38
+ authority: environment.authority,
39
+ client_id: environment.clientId,
40
+ scope: 'openid profile offline_access',
41
+ automaticSilentRenew: false,
42
+ },
43
+ web: {
44
+ settings: {
45
+ redirect_uri: `${window.location.origin}/callback`,
46
+ post_logout_redirect_uri: `${window.location.origin}/logout-callback`,
47
+ },
48
+ },
49
+ native: {
50
+ settings: {
51
+ redirect_uri: 'capacitor-oidc-example:/callback',
52
+ post_logout_redirect_uri: 'capacitor-oidc-example:/logout-callback',
53
+ },
54
+ options: { storageNamespace: 'angular-example' },
55
+ },
56
+ });
57
+ }
58
+ ```
59
+
60
+ The runnable example keeps automatic renewal off so the bundled realm's
61
+ 30-second test tokens can be renewed with the example's button. For a normal
62
+ provider, enable automatic renewal and keep its expiry-notification threshold
63
+ below the provider's access-token lifetime.
64
+
65
+ ## Initialize from the root component
66
+
67
+ The service handles browser callbacks before exposing the restored user:
68
+
69
+ ```ts
70
+ async initialize() {
71
+ const auth = await this.manager;
72
+
73
+ auth.events.addUserLoaded((user) => this.user.set(user));
74
+ auth.events.addUserUnloaded(() => this.user.set(null));
75
+
76
+ if (window.location.pathname === '/callback') {
77
+ await auth.signinCallback();
78
+ window.history.replaceState({}, '', '/');
79
+ } else if (window.location.pathname === '/logout-callback') {
80
+ await auth.signoutCallback();
81
+ window.history.replaceState({}, '', '/');
82
+ }
83
+
84
+ this.user.set(await auth.getUser());
85
+ }
86
+ ```
87
+
88
+ Call it once from the root component:
89
+
90
+ ```ts
91
+ export class AppComponent implements OnInit {
92
+ readonly oidc = inject(OidcService);
93
+
94
+ ngOnInit() {
95
+ void this.oidc.initialize();
96
+ }
97
+ }
98
+ ```
99
+
100
+ Native callbacks finish inside the plugin and do not need Angular Router
101
+ routes.
102
+
103
+ ## Bind actions
104
+
105
+ The root service can expose `signin()`, `getValidUser(60)`, and `signout()` as
106
+ methods. Components read the current `user` signal and do not hold their own
107
+ manager.
108
+
109
+ Call `dispose()` only when the Angular application is shutting down.
110
+
111
+ ## Run the example
112
+
113
+ ```sh
114
+ npm ci
115
+ npm run build
116
+ npm run e2e:keycloak:up
117
+ npm --prefix examples/angular install
118
+ npm --prefix examples/angular run dev
119
+ ```
120
+
121
+ Sign in with the disposable `demo` / `demo` account. Stop the local realm with
122
+ `npm run e2e:keycloak:down` when finished.
123
+
124
+ This local realm workflow is for the browser example. Android resolves
125
+ `localhost` inside the device or emulator, and native platforms should not use
126
+ the example's cleartext HTTP authority. Before building for iOS or Android,
127
+ replace `environment.authority` with an HTTPS authority reachable from that
128
+ device and register the configured native callback scheme at the provider. See
129
+ [iOS and Android setup](../PLATFORM_SETUP.md).
@@ -0,0 +1,134 @@
1
+ # React integration
2
+
3
+ The [React example](/examples/react/) creates its OIDC manager outside the
4
+ component tree, restores the user when the application mounts, and keeps React
5
+ state aligned with the manager's user events.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm install capacitor-oidc @capacitor/app
11
+ npx cap sync
12
+ ```
13
+
14
+ ## Create the manager outside React
15
+
16
+ A module-level promise prevents component renders from constructing multiple
17
+ managers:
18
+
19
+ ```ts
20
+ // src/auth.ts
21
+ import { CapacitorUserManager } from 'capacitor-oidc';
22
+
23
+ const manager = CapacitorUserManager.create({
24
+ common: {
25
+ authority: import.meta.env.VITE_OIDC_AUTHORITY,
26
+ client_id: import.meta.env.VITE_OIDC_CLIENT_ID,
27
+ scope: 'openid profile offline_access',
28
+ automaticSilentRenew: false,
29
+ },
30
+ web: {
31
+ settings: {
32
+ redirect_uri: `${window.location.origin}/callback`,
33
+ post_logout_redirect_uri: `${window.location.origin}/logout-callback`,
34
+ },
35
+ },
36
+ native: {
37
+ settings: {
38
+ redirect_uri: 'capacitor-oidc-example:/callback',
39
+ post_logout_redirect_uri: 'capacitor-oidc-example:/logout-callback',
40
+ },
41
+ options: { storageNamespace: 'react-example' },
42
+ },
43
+ });
44
+
45
+ export function getUserManager() {
46
+ return manager;
47
+ }
48
+
49
+ let callback: Promise<void> | undefined;
50
+
51
+ export function completeWebCallback(auth: CapacitorUserManager) {
52
+ callback ??= (async () => {
53
+ if (window.location.pathname === '/callback') {
54
+ await auth.signinCallback();
55
+ window.history.replaceState({}, '', '/');
56
+ } else if (window.location.pathname === '/logout-callback') {
57
+ await auth.signoutCallback();
58
+ window.history.replaceState({}, '', '/');
59
+ }
60
+ })();
61
+
62
+ return callback;
63
+ }
64
+ ```
65
+
66
+ The runnable example supplies local Keycloak defaults and keeps automatic
67
+ renewal off so its 30-second test tokens can be renewed with the example's
68
+ button. For a normal provider, enable automatic renewal and keep its
69
+ expiry-notification threshold below the provider's access-token lifetime. Set
70
+ the two Vite environment variables for your provider.
71
+
72
+ ## Initialize from the application root
73
+
74
+ Complete browser callbacks, subscribe to user events, and restore the stored
75
+ user from the root component:
76
+
77
+ ```tsx
78
+ useEffect(() => {
79
+ let active = true;
80
+ let auth: CapacitorUserManager | undefined;
81
+ const userLoaded = (user: User) => active && setUser(user);
82
+ const userUnloaded = () => active && setUser(null);
83
+
84
+ void getUserManager().then(async (manager) => {
85
+ if (!active) return;
86
+ auth = manager;
87
+ manager.events.addUserLoaded(userLoaded);
88
+ manager.events.addUserUnloaded(userUnloaded);
89
+ await completeWebCallback(manager);
90
+ const storedUser = await manager.getUser();
91
+ if (active) setUser(storedUser);
92
+ });
93
+
94
+ return () => {
95
+ active = false;
96
+ auth?.events.removeUserLoaded(userLoaded);
97
+ auth?.events.removeUserUnloaded(userUnloaded);
98
+ };
99
+ }, []);
100
+ ```
101
+
102
+ Native callbacks complete inside the plugin and do not require React Router
103
+ routes.
104
+
105
+ ## Bind actions
106
+
107
+ ```ts
108
+ await auth.signin();
109
+ setUser(await auth.getValidUser(60));
110
+ await auth.signout();
111
+ ```
112
+
113
+ Retain the manager when React routes change. Call `dispose()` only when the
114
+ whole application is shutting down.
115
+
116
+ ## Run the example
117
+
118
+ ```sh
119
+ npm ci
120
+ npm run build
121
+ npm run e2e:keycloak:up
122
+ npm --prefix examples/react install
123
+ npm --prefix examples/react run dev
124
+ ```
125
+
126
+ Sign in with the disposable `demo` / `demo` account. Stop the local realm with
127
+ `npm run e2e:keycloak:down` when finished.
128
+
129
+ This local realm workflow is for the browser example. Android resolves
130
+ `localhost` inside the device or emulator, and native platforms should not use
131
+ the example's cleartext HTTP authority. Before building for iOS or Android, set
132
+ `VITE_OIDC_AUTHORITY` to an HTTPS authority reachable from that device and
133
+ register the configured native callback scheme at the provider. See
134
+ [iOS and Android setup](../PLATFORM_SETUP.md).
@@ -0,0 +1,118 @@
1
+ # Vue integration
2
+
3
+ The [Vue example](/examples/vue/) is a small Capacitor application with one
4
+ OIDC manager for the application lifetime. It restores the stored user when
5
+ Vue mounts, completes browser callbacks, and exposes sign-in, renewal, and
6
+ sign-out actions.
7
+
8
+ ## Install
9
+
10
+ ```sh
11
+ npm install capacitor-oidc @capacitor/app
12
+ npx cap sync
13
+ ```
14
+
15
+ ## Create the manager once
16
+
17
+ Keep manager creation outside Vue components so route changes cannot create a
18
+ second manager:
19
+
20
+ ```ts
21
+ // src/auth.ts
22
+ import { CapacitorUserManager } from 'capacitor-oidc';
23
+
24
+ const manager = CapacitorUserManager.create({
25
+ common: {
26
+ authority: import.meta.env.VITE_OIDC_AUTHORITY,
27
+ client_id: import.meta.env.VITE_OIDC_CLIENT_ID,
28
+ scope: 'openid profile offline_access',
29
+ automaticSilentRenew: false,
30
+ },
31
+ web: {
32
+ settings: {
33
+ redirect_uri: `${window.location.origin}/callback`,
34
+ post_logout_redirect_uri: `${window.location.origin}/logout-callback`,
35
+ },
36
+ },
37
+ native: {
38
+ settings: {
39
+ redirect_uri: 'capacitor-oidc-example:/callback',
40
+ post_logout_redirect_uri: 'capacitor-oidc-example:/logout-callback',
41
+ },
42
+ options: { storageNamespace: 'vue-example' },
43
+ },
44
+ });
45
+
46
+ export function getUserManager() {
47
+ return manager;
48
+ }
49
+ ```
50
+
51
+ The runnable example supplies local Keycloak defaults as a convenience and
52
+ keeps automatic renewal off so its 30-second test tokens can be renewed with
53
+ the example's button. For a normal provider, enable automatic renewal and keep
54
+ its expiry-notification threshold below the provider's access-token lifetime.
55
+ Use environment variables for your provider in an application.
56
+
57
+ ## Restore state and handle callbacks
58
+
59
+ Initialize authentication from the root component. Browser callback routes
60
+ must be handled before rendering the restored user:
61
+
62
+ ```ts
63
+ const auth = await getUserManager();
64
+
65
+ if (window.location.pathname === '/callback') {
66
+ await auth.signinCallback();
67
+ window.history.replaceState({}, '', '/');
68
+ } else if (window.location.pathname === '/logout-callback') {
69
+ await auth.signoutCallback();
70
+ window.history.replaceState({}, '', '/');
71
+ }
72
+
73
+ user.value = await auth.getUser();
74
+
75
+ auth.events.addUserLoaded((loadedUser) => {
76
+ user.value = loadedUser;
77
+ });
78
+
79
+ auth.events.addUserUnloaded(() => {
80
+ user.value = null;
81
+ });
82
+ ```
83
+
84
+ Native callbacks are completed by the plugin and do not need Vue Router
85
+ routes.
86
+
87
+ ## Bind actions
88
+
89
+ ```ts
90
+ await auth.signin();
91
+ user.value = await auth.getValidUser(60);
92
+ await auth.signout();
93
+ ```
94
+
95
+ Call `dispose()` only when the application is shutting down, not whenever a
96
+ route component unmounts.
97
+
98
+ ## Run the example
99
+
100
+ Start the repository's local Keycloak realm, then start the Vue app:
101
+
102
+ ```sh
103
+ npm ci
104
+ npm run build
105
+ npm run e2e:keycloak:up
106
+ npm --prefix examples/vue install
107
+ npm --prefix examples/vue run dev
108
+ ```
109
+
110
+ Sign in with the disposable `demo` / `demo` account. Stop the local realm with
111
+ `npm run e2e:keycloak:down` when finished.
112
+
113
+ This local realm workflow is for the browser example. Android resolves
114
+ `localhost` inside the device or emulator, and native platforms should not use
115
+ the example's cleartext HTTP authority. Before building for iOS or Android, set
116
+ `VITE_OIDC_AUTHORITY` to an HTTPS authority reachable from that device and
117
+ register the configured native callback scheme at the provider. See
118
+ [iOS and Android setup](../PLATFORM_SETUP.md).
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "capacitor-oidc",
3
- "version": "0.0.6",
4
- "description": "Capacitor plugin for native OAuth 2.0 and OpenID Connect authentication on iOS and Android, powered by oidc-client-ts.",
3
+ "version": "0.1.1",
4
+ "description": "OAuth 2.0 and OpenID Connect authentication for Capacitor web, iOS, and Android, powered by oidc-client-ts.",
5
5
  "keywords": [
6
6
  "capacitor",
7
7
  "capacitor-plugin",
@@ -11,6 +11,7 @@
11
11
  "openid-connect",
12
12
  "oidc",
13
13
  "pkce",
14
+ "web",
14
15
  "ios",
15
16
  "android",
16
17
  "oidc-client-ts"
@@ -48,6 +49,9 @@
48
49
  "build": "npm run clean && tsc && rollup -c rollup.config.mjs",
49
50
  "check:package": "node -e \"import('./dist/esm/index.js')\"",
50
51
  "clean": "rimraf dist",
52
+ "docs:build": "vitepress build . && cp LICENSE .vitepress/dist/LICENSE",
53
+ "docs:dev": "vitepress dev .",
54
+ "docs:preview": "vitepress preview .",
51
55
  "e2e:keycloak:down": "docker compose -f test/e2e/keycloak/compose.yml down",
52
56
  "e2e:keycloak:up": "test/e2e/keycloak/create-certificate.sh test/e2e/keycloak/.certificates && docker compose -f test/e2e/keycloak/compose.yml up -d",
53
57
  "example:build": "npm --prefix example run build",
@@ -78,6 +82,8 @@
78
82
  "rimraf": "6.1.0",
79
83
  "rollup": "4.62.5",
80
84
  "typescript": "5.9.3",
85
+ "vite": "7.3.6",
86
+ "vitepress": "2.0.0-alpha.19",
81
87
  "vitest": "3.2.7"
82
88
  },
83
89
  "prettier": "@ionic/prettier-config",