@multiplatform.one/keycloak 1.0.2 → 1.0.4

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 (169) hide show
  1. package/package.json +32 -34
  2. package/routes/index.d.ts +1 -1
  3. package/routes/index.js +1 -1
  4. package/src/Authenticated.tsx +12 -13
  5. package/src/apollo.ts +1 -1
  6. package/src/authConfig.ts +2 -0
  7. package/src/hooks/useTokensFromQuery/index.ts +2 -0
  8. package/src/hooks/useTokensFromState.ts +2 -2
  9. package/{lib/@types/expoKeycloakAuth.d.d.mts → src/keycloak/config.ts} +17 -6
  10. package/src/keycloak/context.ts +3 -1
  11. package/src/keycloak/index.ts +1 -0
  12. package/src/keycloak/keycloak.native.ts +49 -15
  13. package/src/keycloak/keycloak.ts +66 -11
  14. package/src/provider/AfterAuth.tsx +2 -0
  15. package/src/provider/AuthProvider/AuthProvider.native.tsx +167 -6
  16. package/src/provider/AuthProvider/AuthProvider.tsx +66 -43
  17. package/src/provider/KeycloakProvider.tsx +12 -2
  18. package/src/routes.ts +19 -0
  19. package/src/session/session.ts +4 -15
  20. package/src/token.ts +13 -5
  21. package/lib/@types/expoKeycloakAuth.d.d.ts +0 -25
  22. package/lib/@types/expoKeycloakAuth.d.js +0 -1
  23. package/lib/@types/expoKeycloakAuth.d.mjs +0 -0
  24. package/lib/Authenticated.d.mts +0 -32
  25. package/lib/Authenticated.d.ts +0 -32
  26. package/lib/Authenticated.js +0 -20
  27. package/lib/Authenticated.mjs +0 -20
  28. package/lib/apollo.d.mts +0 -7
  29. package/lib/apollo.d.ts +0 -7
  30. package/lib/apollo.js +0 -15
  31. package/lib/apollo.mjs +0 -15
  32. package/lib/authConfig.d.mts +0 -12
  33. package/lib/authConfig.d.ts +0 -12
  34. package/lib/authConfig.js +0 -9
  35. package/lib/authConfig.mjs +0 -9
  36. package/lib/chunk-2L5TXBDK.mjs +0 -7
  37. package/lib/chunk-2MYOFYAK.mjs +0 -53
  38. package/lib/chunk-2VDDEPLF.js +0 -27
  39. package/lib/chunk-2VGMJ7WP.js +0 -34
  40. package/lib/chunk-32AVT4GA.js +0 -9
  41. package/lib/chunk-3ZTSLDX7.js +0 -13
  42. package/lib/chunk-6APOOEW6.js +0 -28
  43. package/lib/chunk-6F4PWJZI.js +0 -1
  44. package/lib/chunk-6FRYV4MK.mjs +0 -27
  45. package/lib/chunk-6WBHOY7M.js +0 -50
  46. package/lib/chunk-74FSREBY.js +0 -161
  47. package/lib/chunk-A2V2S4HP.js +0 -22
  48. package/lib/chunk-BUZONXAW.js +0 -1
  49. package/lib/chunk-C6TOWKPW.mjs +0 -245
  50. package/lib/chunk-DNEUOHZQ.mjs +0 -28
  51. package/lib/chunk-EGCPD3BX.js +0 -51
  52. package/lib/chunk-HH32BME2.mjs +0 -0
  53. package/lib/chunk-HTWZLBA4.mjs +0 -13
  54. package/lib/chunk-I5FNGKQ6.js +0 -16
  55. package/lib/chunk-IGSEESI3.mjs +0 -51
  56. package/lib/chunk-JAFUI2JQ.mjs +0 -9
  57. package/lib/chunk-JPQI72HR.mjs +0 -0
  58. package/lib/chunk-KWNIT4G4.mjs +0 -34
  59. package/lib/chunk-L3HTVJ4B.mjs +0 -13
  60. package/lib/chunk-MDAAMGTE.js +0 -1
  61. package/lib/chunk-NQMFZ6RW.mjs +0 -50
  62. package/lib/chunk-OAUYHIWG.mjs +0 -0
  63. package/lib/chunk-OBB23GPX.mjs +0 -0
  64. package/lib/chunk-OYWXU5N7.mjs +0 -161
  65. package/lib/chunk-P57PW2II.js +0 -11
  66. package/lib/chunk-TUJRPTZE.js +0 -13
  67. package/lib/chunk-UESWAK47.mjs +0 -0
  68. package/lib/chunk-UKSHA3X3.mjs +0 -16
  69. package/lib/chunk-VS4M7GMO.js +0 -1
  70. package/lib/chunk-W4LGQZMF.js +0 -245
  71. package/lib/chunk-WBQAMGXK.mjs +0 -0
  72. package/lib/chunk-WMH347OL.mjs +0 -22
  73. package/lib/chunk-WS4IMGVL.js +0 -1
  74. package/lib/chunk-YACGLW5Q.js +0 -7
  75. package/lib/chunk-YYYDFAI4.js +0 -53
  76. package/lib/chunk-ZCBD6HWK.js +0 -1
  77. package/lib/chunk-ZFXKCRJC.mjs +0 -11
  78. package/lib/hooks/index.d.mts +0 -5
  79. package/lib/hooks/index.d.ts +0 -5
  80. package/lib/hooks/index.js +0 -19
  81. package/lib/hooks/index.mjs +0 -19
  82. package/lib/hooks/useAuthConfig.d.mts +0 -6
  83. package/lib/hooks/useAuthConfig.d.ts +0 -6
  84. package/lib/hooks/useAuthConfig.js +0 -8
  85. package/lib/hooks/useAuthConfig.mjs +0 -8
  86. package/lib/hooks/useTokensFromQuery/index.d.mts +0 -3
  87. package/lib/hooks/useTokensFromQuery/index.d.ts +0 -3
  88. package/lib/hooks/useTokensFromQuery/index.js +0 -7
  89. package/lib/hooks/useTokensFromQuery/index.mjs +0 -7
  90. package/lib/hooks/useTokensFromQuery/index.native.d.mts +0 -3
  91. package/lib/hooks/useTokensFromQuery/index.native.d.ts +0 -3
  92. package/lib/hooks/useTokensFromQuery/index.native.js +0 -7
  93. package/lib/hooks/useTokensFromQuery/index.native.mjs +0 -7
  94. package/lib/hooks/useTokensFromState.d.mts +0 -3
  95. package/lib/hooks/useTokensFromState.d.ts +0 -3
  96. package/lib/hooks/useTokensFromState.js +0 -9
  97. package/lib/hooks/useTokensFromState.mjs +0 -9
  98. package/lib/index.d.mts +0 -18
  99. package/lib/index.d.ts +0 -18
  100. package/lib/index.js +0 -70
  101. package/lib/index.mjs +0 -70
  102. package/lib/keycloak/context.d.mts +0 -12
  103. package/lib/keycloak/context.d.ts +0 -12
  104. package/lib/keycloak/context.js +0 -7
  105. package/lib/keycloak/context.mjs +0 -7
  106. package/lib/keycloak/index.d.mts +0 -7
  107. package/lib/keycloak/index.d.ts +0 -7
  108. package/lib/keycloak/index.js +0 -14
  109. package/lib/keycloak/index.mjs +0 -14
  110. package/lib/keycloak/keycloak.d.mts +0 -37
  111. package/lib/keycloak/keycloak.d.ts +0 -37
  112. package/lib/keycloak/keycloak.js +0 -13
  113. package/lib/keycloak/keycloak.mjs +0 -13
  114. package/lib/keycloak/keycloak.native.d.mts +0 -33
  115. package/lib/keycloak/keycloak.native.d.ts +0 -33
  116. package/lib/keycloak/keycloak.native.js +0 -134
  117. package/lib/keycloak/keycloak.native.mjs +0 -134
  118. package/lib/provider/AfterAuth.d.mts +0 -28
  119. package/lib/provider/AfterAuth.d.ts +0 -28
  120. package/lib/provider/AfterAuth.js +0 -18
  121. package/lib/provider/AfterAuth.mjs +0 -18
  122. package/lib/provider/AuthProvider/AuthProvider.d.mts +0 -37
  123. package/lib/provider/AuthProvider/AuthProvider.d.ts +0 -37
  124. package/lib/provider/AuthProvider/AuthProvider.js +0 -19
  125. package/lib/provider/AuthProvider/AuthProvider.mjs +0 -19
  126. package/lib/provider/AuthProvider/AuthProvider.native.d.mts +0 -29
  127. package/lib/provider/AuthProvider/AuthProvider.native.d.ts +0 -29
  128. package/lib/provider/AuthProvider/AuthProvider.native.js +0 -25
  129. package/lib/provider/AuthProvider/AuthProvider.native.mjs +0 -25
  130. package/lib/provider/AuthProvider/index.d.mts +0 -4
  131. package/lib/provider/AuthProvider/index.d.ts +0 -4
  132. package/lib/provider/AuthProvider/index.js +0 -20
  133. package/lib/provider/AuthProvider/index.mjs +0 -20
  134. package/lib/provider/KeycloakProvider.d.mts +0 -34
  135. package/lib/provider/KeycloakProvider.d.ts +0 -34
  136. package/lib/provider/KeycloakProvider.js +0 -21
  137. package/lib/provider/KeycloakProvider.mjs +0 -21
  138. package/lib/provider/index.d.mts +0 -8
  139. package/lib/provider/index.d.ts +0 -8
  140. package/lib/provider/index.js +0 -28
  141. package/lib/provider/index.mjs +0 -28
  142. package/lib/routes.d.mts +0 -34
  143. package/lib/routes.d.ts +0 -34
  144. package/lib/routes.js +0 -131
  145. package/lib/routes.mjs +0 -131
  146. package/lib/session/index.d.mts +0 -3
  147. package/lib/session/index.d.ts +0 -3
  148. package/lib/session/index.js +0 -10
  149. package/lib/session/index.mjs +0 -10
  150. package/lib/session/session.d.mts +0 -18
  151. package/lib/session/session.d.ts +0 -18
  152. package/lib/session/session.js +0 -9
  153. package/lib/session/session.mjs +0 -9
  154. package/lib/session/session.native.d.mts +0 -8
  155. package/lib/session/session.native.d.ts +0 -8
  156. package/lib/session/session.native.js +0 -13
  157. package/lib/session/session.native.mjs +0 -13
  158. package/lib/state.d.mts +0 -12
  159. package/lib/state.d.ts +0 -12
  160. package/lib/state.js +0 -9
  161. package/lib/state.mjs +0 -9
  162. package/lib/token.d.mts +0 -17
  163. package/lib/token.d.ts +0 -17
  164. package/lib/token.js +0 -9
  165. package/lib/token.mjs +0 -9
  166. package/lib/types.d.mts +0 -20
  167. package/lib/types.d.ts +0 -20
  168. package/lib/types.js +0 -1
  169. package/lib/types.mjs +0 -1
@@ -19,15 +19,176 @@
19
19
  * limitations under the License.
20
20
  */
21
21
 
22
- import React from 'react';
23
- import type { AuthProviderProps } from './AuthProvider';
22
+ import React, { useState, useEffect, useMemo, useCallback } from 'react';
23
+ import type { AuthProviderProps, Tokens } from './AuthProvider';
24
+ import type { KeycloakLoginOptions, KeycloakLogoutOptions } from '../../keycloak';
24
25
  import { AfterAuth } from '../AfterAuth';
25
- import { KeycloakProvider as ExpoKeycloakProvider } from 'expo-keycloak-auth';
26
+ import { Keycloak, KeycloakConfigContext } from '../../keycloak';
27
+ import { KeycloakContext } from '../../keycloak/context';
28
+ import { makeRedirectUri, useAuthRequest, useAutoDiscovery, refreshAsync, exchangeCodeAsync } from 'expo-auth-session';
29
+ import { persist, useAuthState } from '../../state';
30
+ import { validOrRefreshableToken, isTokenExpired } from '../../token';
31
+
32
+ const REFRESH_THRESHOLD = 5;
26
33
 
27
34
  export function AuthProvider({ children, keycloakConfig }: AuthProviderProps) {
35
+ const [keycloak, setKeycloak] = useState<Keycloak>();
36
+ const authState = useAuthState();
37
+ const initialRefreshToken = useMemo(() => validOrRefreshableToken((persist && authState.refreshToken) || false), []);
38
+ const keycloakUrl = `${keycloakConfig?.url}/realms/${keycloakConfig?.realm}`;
39
+ const clientId = keycloakConfig.publicClientId || keycloakConfig?.clientId || '';
40
+ const discovery = useAutoDiscovery(keycloakUrl);
41
+ const redirectUri = makeRedirectUri();
42
+ const [tokens, setTokens] = useState<Tokens<false>>({
43
+ refreshToken: initialRefreshToken,
44
+ token: validOrRefreshableToken((persist && authState.token) || false, initialRefreshToken),
45
+ idToken: validOrRefreshableToken((persist && authState.idToken) || false, initialRefreshToken),
46
+ });
47
+ const scopes = useMemo(
48
+ () => [...new Set(['email', 'openid', 'profile', ...(keycloakConfig.scopes || [])])],
49
+ [keycloakConfig.scopes],
50
+ );
51
+ const [request, response, promptAsync] = useAuthRequest(
52
+ {
53
+ clientId,
54
+ redirectUri: redirectUri,
55
+ scopes,
56
+ },
57
+ discovery,
58
+ );
59
+
60
+ useEffect(() => {
61
+ if (!discovery || !response?.type || !request?.codeVerifier || !clientId) return;
62
+ (async () => {
63
+ if (response?.type === 'success' && request.codeVerifier && response.params.code) {
64
+ const tokenResponse = await exchangeCodeAsync(
65
+ {
66
+ clientId,
67
+ code: response.params.code,
68
+ redirectUri,
69
+ scopes,
70
+ extraParams: {
71
+ code_verifier: request.codeVerifier,
72
+ },
73
+ },
74
+ discovery,
75
+ );
76
+ if (tokenResponse) {
77
+ setTokens({
78
+ idToken: tokenResponse.idToken,
79
+ refreshToken: tokenResponse.refreshToken,
80
+ token: tokenResponse.accessToken,
81
+ });
82
+ }
83
+ }
84
+ })();
85
+ }, [
86
+ !discovery,
87
+ request?.codeVerifier,
88
+ response?.type === 'success' ? response?.params?.code : undefined,
89
+ clientId,
90
+ scopes,
91
+ redirectUri,
92
+ ]);
93
+
94
+ const resetTokens = useCallback(() => {
95
+ authState.setIdToken('');
96
+ authState.setRefreshToken('');
97
+ authState.setToken('');
98
+ setTokens({
99
+ refreshToken: undefined,
100
+ token: undefined,
101
+ idToken: undefined,
102
+ });
103
+ }, []);
104
+
105
+ const refreshTokens = useCallback(async () => {
106
+ if (!discovery) return;
107
+ if (typeof tokens.refreshToken === 'string') {
108
+ if (!isTokenExpired(tokens.refreshToken)) {
109
+ const tokenResponse = await refreshAsync(
110
+ {
111
+ clientId,
112
+ refreshToken: tokens.refreshToken,
113
+ scopes,
114
+ },
115
+ discovery,
116
+ );
117
+ if (tokenResponse) {
118
+ setTokens({
119
+ idToken: tokenResponse.idToken,
120
+ refreshToken: tokenResponse.refreshToken,
121
+ token: tokenResponse.accessToken,
122
+ });
123
+ } else {
124
+ resetTokens();
125
+ }
126
+ } else {
127
+ resetTokens();
128
+ }
129
+ } else if (
130
+ authState.refreshToken &&
131
+ typeof authState.refreshToken === 'string' &&
132
+ isTokenExpired(authState.refreshToken)
133
+ ) {
134
+ resetTokens();
135
+ }
136
+ }, [!discovery, tokens.refreshToken, clientId, scopes]);
137
+
138
+ useEffect(() => {
139
+ if (!discovery) return;
140
+ refreshTokens();
141
+ }, [!discovery]);
142
+
143
+ useEffect(() => {
144
+ if (!request) return;
145
+ let refreshHandle: NodeJS.Timeout;
146
+ (async () => {
147
+ if (tokens.token && isTokenExpired(tokens.token)) {
148
+ await refreshTokens();
149
+ } else {
150
+ const _keycloak = new Keycloak(
151
+ keycloakConfig,
152
+ tokens.token || undefined,
153
+ tokens.idToken || undefined,
154
+ tokens.refreshToken || undefined,
155
+ async (options: KeycloakLoginOptions) => {
156
+ await promptAsync(options);
157
+ },
158
+ async (_options: KeycloakLogoutOptions) => {
159
+ resetTokens();
160
+ },
161
+ );
162
+ if (_keycloak.tokenParsed?.exp) {
163
+ const timeout =
164
+ Math.max(
165
+ Math.min(_keycloak.tokenParsed.exp, _keycloak.refreshTokenParsed?.exp || 0) -
166
+ Math.floor(Date.now() / 1000) -
167
+ REFRESH_THRESHOLD,
168
+ REFRESH_THRESHOLD,
169
+ ) * 1000;
170
+ if (timeout > 0) {
171
+ refreshHandle = setTimeout(() => {
172
+ refreshTokens();
173
+ }, timeout);
174
+ } else {
175
+ setTimeout(() => _keycloak.logout(), Math.max(0, timeout + REFRESH_THRESHOLD) * 1000);
176
+ return;
177
+ }
178
+ }
179
+ setKeycloak(_keycloak);
180
+ }
181
+ })();
182
+ return () => {
183
+ if (refreshHandle) clearTimeout(refreshHandle);
184
+ };
185
+ }, [!request, tokens.token, tokens.idToken, tokens.refreshToken, refreshTokens]);
186
+
28
187
  return (
29
- <ExpoKeycloakProvider {...keycloakConfig}>
30
- <AfterAuth>{children}</AfterAuth>
31
- </ExpoKeycloakProvider>
188
+ <KeycloakConfigContext.Provider value={keycloakConfig}>
189
+ <KeycloakContext.Provider value={keycloak}>
190
+ <AfterAuth>{children}</AfterAuth>
191
+ </KeycloakContext.Provider>
192
+ </KeycloakConfigContext.Provider>
32
193
  );
33
194
  }
@@ -19,62 +19,72 @@
19
19
  * limitations under the License.
20
20
  */
21
21
 
22
+ 'use client';
23
+
22
24
  import KeycloakClient from 'keycloak-js';
23
25
  import React, { useEffect, useMemo, useState } from 'react';
24
- import type { KeycloakConfig, KeycloakInitOptions } from 'keycloak-js';
26
+ import type { KeycloakInitOptions } from 'keycloak-js';
27
+ import type { KeycloakConfig } from '../../keycloak';
25
28
  import type { PropsWithChildren } from 'react';
26
29
  import type { SessionProviderProps } from 'next-auth/react';
27
30
  import { AfterAuth } from '../AfterAuth';
28
- import { Keycloak } from '../../keycloak';
31
+ import { Keycloak, KeycloakConfigContext } from '../../keycloak';
29
32
  import { KeycloakContext } from '../../keycloak/context';
30
33
  import { MultiPlatform } from 'multiplatform.one';
31
34
  import { SessionProvider } from 'next-auth/react';
32
35
  import { persist, useAuthState } from '../../state';
33
36
  import { useAuthConfig } from '../../hooks';
34
37
  import { useRouter } from 'next/router';
35
- import { validToken } from '../../token';
38
+ import { validOrRefreshableToken } from '../../token';
36
39
 
37
40
  export interface AuthProviderProps extends PropsWithChildren {
38
- keycloakConfig?: KeycloakConfig;
41
+ keycloakConfig: KeycloakConfig;
39
42
  keycloakInitOptions?: KeycloakInitOptions;
40
43
  sessionProvider?: Omit<SessionProviderProps, 'children'>;
41
44
  }
42
45
 
43
46
  const logger = console;
44
47
 
48
+ export interface Tokens<B = boolean> {
49
+ idToken?: string | B;
50
+ refreshToken?: string | B;
51
+ token?: string | B;
52
+ }
53
+
45
54
  export function AuthProvider({ children, sessionProvider, keycloakInitOptions, keycloakConfig }: AuthProviderProps) {
46
55
  const { debug, messageHandlerKeys } = useAuthConfig();
47
56
  const { query } = MultiPlatform.isNext ? useRouter() : { query: {} };
48
57
  const authState = useAuthState();
49
58
  const [keycloak, setKeycloak] = useState<Keycloak>();
50
- const [refreshToken, setRefreshToken] = useState<string | boolean | undefined>(
51
- validToken(
52
- MultiPlatform.isIframe
53
- ? ('refreshToken' in query && (query.refreshToken?.toString() || true)) ||
54
- (persist && authState.refreshToken) ||
55
- false
56
- : undefined,
57
- ),
59
+ const initialRefreshToken = useMemo(
60
+ () =>
61
+ validOrRefreshableToken(
62
+ MultiPlatform.isIframe
63
+ ? ('refreshToken' in query && (query.refreshToken?.toString() || true)) ||
64
+ (persist && authState.refreshToken) ||
65
+ false
66
+ : undefined,
67
+ ),
68
+ [],
58
69
  );
59
- const [token, setToken] = useState<string | boolean | undefined>(
60
- validToken(
70
+ const [tokens, setTokens] = useState<Tokens>({
71
+ refreshToken: initialRefreshToken,
72
+ token: validOrRefreshableToken(
61
73
  MultiPlatform.isIframe
62
74
  ? ('token' in query && (query.token?.toString() || true)) || (persist && authState.token) || false
63
75
  : undefined,
64
- refreshToken,
76
+ initialRefreshToken,
65
77
  ),
66
- );
67
- const [idToken, setIdToken] = useState<string | boolean | undefined>(
68
- validToken(
78
+ idToken: validOrRefreshableToken(
69
79
  MultiPlatform.isIframe
70
80
  ? ('idToken' in query && (query.idToken?.toString() || true)) || (persist && authState.idToken) || false
71
81
  : undefined,
72
- refreshToken,
82
+ initialRefreshToken,
73
83
  ),
74
- );
84
+ });
75
85
 
76
86
  useEffect(() => {
77
- if (token !== true && refreshToken !== true && idToken !== true) return;
87
+ if (tokens.token !== true && tokens.refreshToken !== true && tokens.idToken !== true) return;
78
88
  if (debug) logger.debug('post message', JSON.stringify({ type: 'LOADED' }));
79
89
  (messageHandlerKeys || []).forEach((key: string) => {
80
90
  window?.webkit?.messageHandlers?.[key]?.postMessage(JSON.stringify({ type: 'LOADED' }));
@@ -84,7 +94,7 @@ export function AuthProvider({ children, sessionProvider, keycloakInitOptions, k
84
94
  }, []);
85
95
 
86
96
  useEffect(() => {
87
- if (refreshToken !== true) return;
97
+ if (tokens.refreshToken !== true) return;
88
98
  const messageCallback = (e: MessageEvent<any>) => {
89
99
  let data = e?.data || null;
90
100
  if (typeof data === 'string') {
@@ -97,7 +107,7 @@ export function AuthProvider({ children, sessionProvider, keycloakInitOptions, k
97
107
  if (!message.type) return;
98
108
  if (message.type.toUpperCase() === 'REFRESH_TOKEN' && message.payload) {
99
109
  if (debug) logger.debug('setting refresh token', message.payload);
100
- setRefreshToken(validToken(message.payload));
110
+ setTokens((tokens) => ({ ...tokens, refreshToken: validOrRefreshableToken(message.payload) }));
101
111
  window.removeEventListener('message', messageCallback);
102
112
  }
103
113
  };
@@ -117,7 +127,7 @@ export function AuthProvider({ children, sessionProvider, keycloakInitOptions, k
117
127
  }, []);
118
128
 
119
129
  useEffect(() => {
120
- if (token !== true) return;
130
+ if (tokens.token !== true) return;
121
131
  const messageCallback = (e: MessageEvent<any>) => {
122
132
  let data = e?.data || null;
123
133
  if (typeof data === 'string') {
@@ -130,7 +140,7 @@ export function AuthProvider({ children, sessionProvider, keycloakInitOptions, k
130
140
  if (!message.type) return;
131
141
  if (message.type.toUpperCase() === 'TOKEN' && message.payload) {
132
142
  if (debug) logger.debug('setting token', message.payload);
133
- setToken(validToken(message.payload, refreshToken));
143
+ setTokens((tokens) => ({ ...tokens, token: validOrRefreshableToken(message.payload, tokens.refreshToken) }));
134
144
  window.removeEventListener('message', messageCallback);
135
145
  }
136
146
  };
@@ -150,7 +160,7 @@ export function AuthProvider({ children, sessionProvider, keycloakInitOptions, k
150
160
  }, []);
151
161
 
152
162
  useEffect(() => {
153
- if (idToken !== true) return;
163
+ if (tokens.idToken !== true) return;
154
164
  const messageCallback = (e: MessageEvent<any>) => {
155
165
  let data = e?.data || null;
156
166
  if (typeof data === 'string') {
@@ -163,7 +173,7 @@ export function AuthProvider({ children, sessionProvider, keycloakInitOptions, k
163
173
  if (!message.type) return;
164
174
  if (message.type.toUpperCase() === 'ID_TOKEN' && message.payload) {
165
175
  if (debug) logger.debug('setting id token', message.payload);
166
- setIdToken(validToken(message.payload, refreshToken));
176
+ setTokens((tokens) => ({ ...tokens, idToken: validOrRefreshableToken(message.payload, tokens.refreshToken) }));
167
177
  window.removeEventListener('message', messageCallback);
168
178
  }
169
179
  };
@@ -188,39 +198,50 @@ export function AuthProvider({ children, sessionProvider, keycloakInitOptions, k
188
198
  ? new KeycloakClient({
189
199
  url: keycloakConfig.url,
190
200
  realm: keycloakConfig.realm,
191
- clientId: keycloakConfig.clientId,
201
+ clientId: keycloakConfig.publicClientId || keycloakConfig.clientId,
192
202
  })
193
203
  : undefined,
194
- [keycloakInitOptions, keycloakConfig, token, refreshToken, idToken],
204
+ [keycloakInitOptions, keycloakConfig, tokens.token, tokens.refreshToken, tokens.idToken],
195
205
  );
196
206
 
197
207
  const initOptions = useMemo(() => {
198
208
  if (!MultiPlatform.isIframe) return;
199
- const initOptions = {
209
+ const initOptions: KeycloakInitOptions = {
200
210
  ...defaultKeycloakInitOptions,
201
211
  ...keycloakInitOptions,
212
+ scope: [
213
+ ...new Set([
214
+ 'email',
215
+ 'openid',
216
+ 'profile',
217
+ ...(defaultKeycloakInitOptions.scope?.split(' ') || []),
218
+ ...(keycloakInitOptions?.scope?.split(' ') || []),
219
+ ...(keycloakConfig.scopes || []),
220
+ ]),
221
+ ].join(' '),
202
222
  };
203
223
  if (debug) initOptions.enableLogging = true;
204
- if (token && typeof token === 'string') {
205
- initOptions.token = token;
224
+ if (tokens.token && typeof tokens.token === 'string') {
225
+ initOptions.token = tokens.token;
206
226
  initOptions.timeSkew = 0;
207
- if (idToken && typeof idToken === 'string') initOptions.idToken = idToken;
208
- if (refreshToken && typeof refreshToken === 'string') initOptions.refreshToken = refreshToken;
227
+ if (tokens.idToken && typeof tokens.idToken === 'string') initOptions.idToken = tokens.idToken;
228
+ if (tokens.refreshToken && typeof tokens.refreshToken === 'string')
229
+ initOptions.refreshToken = tokens.refreshToken;
209
230
  }
210
- if ((typeof window !== 'undefined' && window.self !== window.top) || (token && !refreshToken)) {
231
+ if ((typeof window !== 'undefined' && window.self !== window.top) || (tokens.token && !tokens.refreshToken)) {
211
232
  initOptions.checkLoginIframe = false;
212
233
  initOptions.flow = 'implicit';
213
234
  initOptions.onLoad = undefined;
214
235
  }
215
236
  return initOptions;
216
- }, [keycloakInitOptions, token, idToken, refreshToken]);
237
+ }, [keycloakInitOptions, keycloakConfig.scopes, tokens.token, tokens.idToken, tokens.refreshToken]);
217
238
 
218
239
  useEffect(() => {
219
240
  function onError() {
220
- if (keycloakClient && initOptions) setKeycloak(new Keycloak(keycloakClient));
241
+ if (keycloakClient && initOptions) setKeycloak(new Keycloak(keycloakConfig, keycloakClient));
221
242
  }
222
243
  function onUpdate() {
223
- if (keycloakClient && initOptions) setKeycloak(new Keycloak(keycloakClient));
244
+ if (keycloakClient && initOptions) setKeycloak(new Keycloak(keycloakConfig, keycloakClient));
224
245
  }
225
246
  if (keycloakClient && initOptions) {
226
247
  (async () => {
@@ -234,20 +255,22 @@ export function AuthProvider({ children, sessionProvider, keycloakInitOptions, k
234
255
  try {
235
256
  await keycloakClient.updateToken(5);
236
257
  } catch (err) {
237
- setKeycloak(new Keycloak());
258
+ setKeycloak(new Keycloak(keycloakConfig));
238
259
  }
239
260
  };
240
261
  const authenticated = await keycloakClient.init(initOptions);
241
- setKeycloak(new Keycloak(authenticated ? keycloakClient : undefined));
262
+ setKeycloak(new Keycloak(keycloakConfig, authenticated ? keycloakClient : undefined));
242
263
  })();
243
264
  }
244
265
  }, []);
245
266
 
246
267
  return (
247
268
  <SessionProvider {...sessionProvider}>
248
- <KeycloakContext.Provider value={keycloak}>
249
- <AfterAuth>{children}</AfterAuth>
250
- </KeycloakContext.Provider>
269
+ <KeycloakConfigContext.Provider value={keycloakConfig}>
270
+ <KeycloakContext.Provider value={keycloak}>
271
+ <AfterAuth>{children}</AfterAuth>
272
+ </KeycloakContext.Provider>
273
+ </KeycloakConfigContext.Provider>
251
274
  </SessionProvider>
252
275
  );
253
276
  }
@@ -29,11 +29,20 @@ import { AuthProvider } from './AuthProvider';
29
29
  export interface KeycloakProviderProps extends PropsWithChildren, AuthConfig {
30
30
  baseUrl?: string;
31
31
  clientId?: string;
32
+ publicClientId?: string;
32
33
  realm?: string;
33
34
  session?: Session;
34
35
  }
35
36
 
36
- export function KeycloakProvider({ children, debug, realm, clientId, baseUrl, session }: KeycloakProviderProps) {
37
+ export function KeycloakProvider({
38
+ children,
39
+ debug,
40
+ realm,
41
+ clientId,
42
+ baseUrl,
43
+ publicClientId,
44
+ session,
45
+ }: KeycloakProviderProps) {
37
46
  const authConfig = useMemo(() => ({ debug }), [debug]);
38
47
 
39
48
  return (
@@ -41,8 +50,9 @@ export function KeycloakProvider({ children, debug, realm, clientId, baseUrl, se
41
50
  <AuthProvider
42
51
  sessionProvider={{ session }}
43
52
  keycloakConfig={{
44
- realm: realm || 'main',
45
53
  clientId: clientId || 'app',
54
+ publicClientId,
55
+ realm: realm || 'main',
46
56
  url: baseUrl,
47
57
  }}
48
58
  >
package/src/routes.ts CHANGED
@@ -74,11 +74,29 @@ export function createLogoutHandler(options: AuthHandlerOptions) {
74
74
 
75
75
  export function createNextAuthOptions(options: AuthHandlerOptions) {
76
76
  if (_nextAuth) return _nextAuth;
77
+ const authorization =
78
+ options.keycloak.authorization && typeof options.keycloak.authorization === 'object'
79
+ ? options.keycloak.authorization
80
+ : {};
77
81
  _nextAuth = {
78
82
  ...options.nextAuth,
79
83
  providers: [
80
84
  KeycloakProvider({
81
85
  issuer: `${options.keycloak?.baseUrl || defaults.keycloak.baseUrl}/realms/${options.keycloak?.realm || defaults.keycloak.realm}`,
86
+ authorization: {
87
+ ...authorization,
88
+ params: {
89
+ scope: [
90
+ ...new Set([
91
+ 'email',
92
+ 'openid',
93
+ 'profile',
94
+ ...(authorization.params?.scope?.split(' ') || []),
95
+ ...(options.scopes || []),
96
+ ]),
97
+ ].join(' '),
98
+ },
99
+ },
82
100
  ...options.keycloak,
83
101
  }),
84
102
  ...(options.nextAuth?.providers || []),
@@ -130,6 +148,7 @@ export interface AuthHandlerOptions {
130
148
  baseUrl: string;
131
149
  keycloak: KeycloakOptions;
132
150
  nextAuth?: Partial<AuthOptions>;
151
+ scopes?: string[];
133
152
  }
134
153
 
135
154
  export interface KeycloakOptions extends OAuthUserConfig<any> {
@@ -20,21 +20,13 @@
20
20
  */
21
21
 
22
22
  import type { AuthOptions, Session as NextSession } from 'next-auth';
23
+ import type { SessionContextValue as NextSessionContextValue, UseSessionOptions } from 'next-auth/react';
23
24
  import { MultiPlatform } from 'multiplatform.one';
24
25
  import { useSession as useNextSession, getSession as getNextSession } from 'next-auth/react';
25
- import type {
26
- GetSessionParams,
27
- SessionContextValue as NextSessionContextValue,
28
- UseSessionOptions,
29
- } from 'next-auth/react';
30
26
 
31
27
  let _getServerSession: ((options?: AuthOptions, req?: any, res?: any) => Promise<Session | null>) | undefined;
32
28
 
33
- export async function getSession(
34
- options?: AuthOptions | GetSessionParams,
35
- req?: any,
36
- res?: any,
37
- ): Promise<Session | null> {
29
+ export async function getSession(options?: AuthOptions, req?: any, res?: any): Promise<Session | null> {
38
30
  if (MultiPlatform.isServer) {
39
31
  if (typeof _getServerSession === 'function') return _getServerSession(options as AuthOptions, req, res);
40
32
  const getSession = (await import('next-auth')).getServerSession;
@@ -43,17 +35,14 @@ export async function getSession(
43
35
  };
44
36
  return _getServerSession(options as AuthOptions, req, res);
45
37
  }
46
- return getNextSession(options as GetSessionParams);
38
+ return getNextSession();
47
39
  }
48
40
 
49
41
  export type SessionContextValue<R extends boolean> = Partial<
50
42
  Omit<NextSessionContextValue<R> & { session: Session }, 'data'>
51
43
  >;
52
44
 
53
- let useSession = <R extends boolean>(
54
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
55
- options?: UseSessionOptions<R>,
56
- ): SessionContextValue<R> => {
45
+ let useSession = <R extends boolean>(_options?: UseSessionOptions<R>): SessionContextValue<R> => {
57
46
  return {};
58
47
  };
59
48
 
package/src/token.ts CHANGED
@@ -22,21 +22,29 @@
22
22
  import type { KeycloakTokenParsed } from 'keycloak-js';
23
23
  import { jwtDecode } from 'jwt-decode';
24
24
 
25
- export function validToken(token?: string | boolean, refreshToken?: string | boolean) {
25
+ export function validOrRefreshableToken(
26
+ token?: string | false,
27
+ refreshToken?: string | boolean,
28
+ ): string | false | undefined;
29
+ export function validOrRefreshableToken(
30
+ token?: string | boolean,
31
+ refreshToken?: string | boolean,
32
+ ): string | boolean | undefined;
33
+ export function validOrRefreshableToken(token?: string | boolean, refreshToken?: string | boolean) {
26
34
  if (typeof token === 'undefined') return;
27
35
  if (!token) return false;
28
36
  if (typeof token !== 'string') return token;
29
37
  if (refreshToken) {
30
- if (refreshToken === true || isTokenValid(refreshToken)) return token;
38
+ if (refreshToken === true || !isTokenExpired(refreshToken)) return token;
31
39
  return false;
32
40
  }
33
- return isTokenValid(token) ? token : false;
41
+ return isTokenExpired(token) ? false : token;
34
42
  }
35
43
 
36
- export function isTokenValid(token: string) {
44
+ export function isTokenExpired(token: string) {
37
45
  const { exp } = jwtDecode(token) as TokenParsed;
38
46
  if (!exp) return false;
39
- return Math.floor(Date.now() / 1000) <= exp;
47
+ return Math.floor(Date.now() / 1000) >= exp;
40
48
  }
41
49
 
42
50
  export interface TokenParsed extends KeycloakTokenParsed {
@@ -1,25 +0,0 @@
1
- /*
2
- * File: /src/@types/expoKeycloakAuth.d.ts
3
- * Project: @multiplatform.one/keycloak
4
- * File Created: 22-06-2023 10:07:56
5
- * Author: Clay Risser
6
- * -----
7
- * BitSpur (c) Copyright 2021 - 2023
8
- *
9
- * Licensed under the Apache License, Version 2.0 (the "License");
10
- * you may not use this file except in compliance with the License.
11
- * You may obtain a copy of the License at
12
- *
13
- * http://www.apache.org/licenses/LICENSE-2.0
14
- *
15
- * Unless required by applicable law or agreed to in writing, software
16
- * distributed under the License is distributed on an "AS IS" BASIS,
17
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
- * See the License for the specific language governing permissions and
19
- * limitations under the License.
20
- */
21
-
22
- declare module 'expo-keycloak-auth' {
23
- export function useKeycloak(): any;
24
- export const KeycloakProvider: any;
25
- }
@@ -1 +0,0 @@
1
- "use strict";
File without changes
@@ -1,32 +0,0 @@
1
- import React__default, { PropsWithChildren, ComponentType } from 'react';
2
-
3
- /**
4
- * File: /src/Authenticated.tsx
5
- * Project: @multiplatform.one/keycloak
6
- * File Created: 22-06-2023 10:07:56
7
- * Author: Clay Risser
8
- * -----
9
- * BitSpur (c) Copyright 2021 - 2023
10
- *
11
- * Licensed under the Apache License, Version 2.0 (the "License");
12
- * you may not use this file except in compliance with the License.
13
- * You may obtain a copy of the License at
14
- *
15
- * http://www.apache.org/licenses/LICENSE-2.0
16
- *
17
- * Unless required by applicable law or agreed to in writing, software
18
- * distributed under the License is distributed on an "AS IS" BASIS,
19
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
- * See the License for the specific language governing permissions and
21
- * limitations under the License.
22
- */
23
-
24
- interface AuthenticatedProps extends PropsWithChildren {
25
- disabled?: boolean;
26
- loadingComponent?: ComponentType;
27
- loggedOutComponent?: ComponentType;
28
- }
29
- declare function Authenticated({ children, disabled, loggedOutComponent, loadingComponent }: AuthenticatedProps): React__default.JSX.Element;
30
- declare function withAuthenticated<P extends object>(Component: ComponentType<P>): (props: P) => React__default.JSX.Element;
31
-
32
- export { Authenticated, type AuthenticatedProps, withAuthenticated };
@@ -1,32 +0,0 @@
1
- import React__default, { PropsWithChildren, ComponentType } from 'react';
2
-
3
- /**
4
- * File: /src/Authenticated.tsx
5
- * Project: @multiplatform.one/keycloak
6
- * File Created: 22-06-2023 10:07:56
7
- * Author: Clay Risser
8
- * -----
9
- * BitSpur (c) Copyright 2021 - 2023
10
- *
11
- * Licensed under the Apache License, Version 2.0 (the "License");
12
- * you may not use this file except in compliance with the License.
13
- * You may obtain a copy of the License at
14
- *
15
- * http://www.apache.org/licenses/LICENSE-2.0
16
- *
17
- * Unless required by applicable law or agreed to in writing, software
18
- * distributed under the License is distributed on an "AS IS" BASIS,
19
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
- * See the License for the specific language governing permissions and
21
- * limitations under the License.
22
- */
23
-
24
- interface AuthenticatedProps extends PropsWithChildren {
25
- disabled?: boolean;
26
- loadingComponent?: ComponentType;
27
- loggedOutComponent?: ComponentType;
28
- }
29
- declare function Authenticated({ children, disabled, loggedOutComponent, loadingComponent }: AuthenticatedProps): React__default.JSX.Element;
30
- declare function withAuthenticated<P extends object>(Component: ComponentType<P>): (props: P) => React__default.JSX.Element;
31
-
32
- export { Authenticated, type AuthenticatedProps, withAuthenticated };