@multiplatform.one/keycloak 1.0.1 → 1.0.3

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 +159 -6
  16. package/src/provider/AuthProvider/AuthProvider.tsx +66 -43
  17. package/src/provider/KeycloakProvider.tsx +12 -2
  18. package/src/routes.ts +54 -20
  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-267L3NLO.mjs +0 -53
  37. package/lib/chunk-2L5TXBDK.mjs +0 -7
  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-3KAZ37SK.mjs +0 -51
  42. package/lib/chunk-3ZTSLDX7.js +0 -13
  43. package/lib/chunk-5CWGWY74.mjs +0 -28
  44. package/lib/chunk-6F4PWJZI.js +0 -1
  45. package/lib/chunk-6FRYV4MK.mjs +0 -27
  46. package/lib/chunk-6Q4DC5JX.mjs +0 -50
  47. package/lib/chunk-7NZPJ2EN.mjs +0 -161
  48. package/lib/chunk-A2V2S4HP.js +0 -22
  49. package/lib/chunk-BUZONXAW.js +0 -1
  50. package/lib/chunk-HH32BME2.mjs +0 -0
  51. package/lib/chunk-HTWZLBA4.mjs +0 -13
  52. package/lib/chunk-I5FNGKQ6.js +0 -16
  53. package/lib/chunk-JAFUI2JQ.mjs +0 -9
  54. package/lib/chunk-JPQI72HR.mjs +0 -0
  55. package/lib/chunk-K2FTCH55.js +0 -50
  56. package/lib/chunk-KWNIT4G4.mjs +0 -34
  57. package/lib/chunk-L3HTVJ4B.mjs +0 -13
  58. package/lib/chunk-MDAAMGTE.js +0 -1
  59. package/lib/chunk-NCIQ66UP.js +0 -51
  60. package/lib/chunk-NWZCPYQP.js +0 -161
  61. package/lib/chunk-OAUYHIWG.mjs +0 -0
  62. package/lib/chunk-OBB23GPX.mjs +0 -0
  63. package/lib/chunk-P57PW2II.js +0 -11
  64. package/lib/chunk-S5HUGQLF.js +0 -28
  65. package/lib/chunk-TUJRPTZE.js +0 -13
  66. package/lib/chunk-UESWAK47.mjs +0 -0
  67. package/lib/chunk-UKSHA3X3.mjs +0 -16
  68. package/lib/chunk-VFQKAKK4.js +0 -245
  69. package/lib/chunk-VPYXLUMD.js +0 -53
  70. package/lib/chunk-VS4M7GMO.js +0 -1
  71. package/lib/chunk-W4PGLIAZ.mjs +0 -245
  72. package/lib/chunk-WBQAMGXK.mjs +0 -0
  73. package/lib/chunk-WMH347OL.mjs +0 -22
  74. package/lib/chunk-WS4IMGVL.js +0 -1
  75. package/lib/chunk-YACGLW5Q.js +0 -7
  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 -27
  143. package/lib/routes.d.ts +0 -27
  144. package/lib/routes.js +0 -127
  145. package/lib/routes.mjs +0 -127
  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,168 @@
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';
26
31
 
27
32
  export function AuthProvider({ children, keycloakConfig }: AuthProviderProps) {
33
+ const [keycloak, setKeycloak] = useState<Keycloak>();
34
+ const authState = useAuthState();
35
+ const initialRefreshToken = useMemo(() => validOrRefreshableToken((persist && authState.refreshToken) || false), []);
36
+ const keycloakUrl = `${keycloakConfig?.url}/realms/${keycloakConfig?.realm}`;
37
+ const clientId = keycloakConfig.publicClientId || keycloakConfig?.clientId || '';
38
+ const discovery = useAutoDiscovery(keycloakUrl);
39
+ const redirectUri = makeRedirectUri();
40
+ const [tokens, setTokens] = useState<Tokens<false>>({
41
+ refreshToken: initialRefreshToken,
42
+ token: validOrRefreshableToken((persist && authState.token) || false, initialRefreshToken),
43
+ idToken: validOrRefreshableToken((persist && authState.idToken) || false, initialRefreshToken),
44
+ });
45
+ const scopes = useMemo(
46
+ () => [...new Set(['email', 'openid', 'profile', ...(keycloakConfig.scopes || [])])],
47
+ [keycloakConfig.scopes],
48
+ );
49
+ const [request, response, promptAsync] = useAuthRequest(
50
+ {
51
+ clientId,
52
+ redirectUri: redirectUri,
53
+ scopes,
54
+ },
55
+ discovery,
56
+ );
57
+
58
+ useEffect(() => {
59
+ if (!discovery || !response?.type || !request?.codeVerifier || !clientId) return;
60
+ (async () => {
61
+ if (response?.type === 'success' && request.codeVerifier && response.params.code) {
62
+ const tokenResponse = await exchangeCodeAsync(
63
+ {
64
+ clientId,
65
+ code: response.params.code,
66
+ redirectUri,
67
+ scopes,
68
+ extraParams: {
69
+ code_verifier: request.codeVerifier,
70
+ },
71
+ },
72
+ discovery,
73
+ );
74
+ if (tokenResponse) {
75
+ setTokens({
76
+ idToken: tokenResponse.idToken,
77
+ refreshToken: tokenResponse.refreshToken,
78
+ token: tokenResponse.accessToken,
79
+ });
80
+ }
81
+ }
82
+ })();
83
+ }, [
84
+ discovery,
85
+ request?.codeVerifier,
86
+ response?.type === 'success' ? response?.params?.code : undefined,
87
+ clientId,
88
+ scopes,
89
+ redirectUri,
90
+ ]);
91
+
92
+ const resetTokens = useCallback(() => {
93
+ authState.setIdToken('');
94
+ authState.setRefreshToken('');
95
+ authState.setToken('');
96
+ setTokens({
97
+ refreshToken: undefined,
98
+ token: undefined,
99
+ idToken: undefined,
100
+ });
101
+ }, [authState]);
102
+
103
+ const refreshTokens = useCallback(async () => {
104
+ if (!discovery) return;
105
+ if (typeof tokens.refreshToken === 'string') {
106
+ if (!isTokenExpired(tokens.refreshToken)) {
107
+ const tokenResponse = await refreshAsync(
108
+ {
109
+ clientId,
110
+ refreshToken: tokens.refreshToken,
111
+ scopes,
112
+ },
113
+ discovery,
114
+ );
115
+ if (tokenResponse) {
116
+ setTokens({
117
+ idToken: tokenResponse.idToken,
118
+ refreshToken: tokenResponse.refreshToken,
119
+ token: tokenResponse.accessToken,
120
+ });
121
+ } else {
122
+ resetTokens();
123
+ }
124
+ } else {
125
+ resetTokens();
126
+ }
127
+ } else if (
128
+ authState.refreshToken &&
129
+ typeof authState.refreshToken === 'string' &&
130
+ isTokenExpired(authState.refreshToken)
131
+ ) {
132
+ resetTokens();
133
+ }
134
+ }, [discovery, resetTokens, authState.refreshToken, tokens.refreshToken, clientId, scopes]);
135
+
136
+ useEffect(() => {
137
+ if (!discovery) return;
138
+ refreshTokens();
139
+ }, [discovery]);
140
+
141
+ useEffect(() => {
142
+ if (!request) return;
143
+ let refreshHandle: NodeJS.Timeout;
144
+ (async () => {
145
+ if (tokens.token && isTokenExpired(tokens.token)) {
146
+ await refreshTokens();
147
+ } else {
148
+ const _keycloak = new Keycloak(
149
+ keycloakConfig,
150
+ tokens.token || undefined,
151
+ tokens.idToken || undefined,
152
+ tokens.refreshToken || undefined,
153
+ async (options: KeycloakLoginOptions) => {
154
+ await promptAsync(options);
155
+ },
156
+ async (_options: KeycloakLogoutOptions) => {
157
+ resetTokens();
158
+ },
159
+ );
160
+ if (_keycloak.refreshTokenParsed?.exp) {
161
+ const timeout = (_keycloak.refreshTokenParsed.exp - Math.floor(Date.now() / 1000) - 10) * 1000;
162
+ if (timeout > 0) {
163
+ refreshHandle = setTimeout(() => {
164
+ refreshTokens();
165
+ }, timeout);
166
+ } else {
167
+ setTimeout(() => _keycloak.logout(), Math.max(0, timeout + 10) * 1000);
168
+ return;
169
+ }
170
+ }
171
+ setKeycloak(_keycloak);
172
+ }
173
+ })();
174
+ return () => {
175
+ if (refreshHandle) clearTimeout(refreshHandle);
176
+ };
177
+ }, [request, tokens.token, tokens.idToken, tokens.refreshToken, refreshTokens, resetTokens]);
178
+
28
179
  return (
29
- <ExpoKeycloakProvider {...keycloakConfig}>
30
- <AfterAuth>{children}</AfterAuth>
31
- </ExpoKeycloakProvider>
180
+ <KeycloakConfigContext.Provider value={keycloakConfig}>
181
+ <KeycloakContext.Provider value={keycloak}>
182
+ <AfterAuth>{children}</AfterAuth>
183
+ </KeycloakContext.Provider>
184
+ </KeycloakConfigContext.Provider>
32
185
  );
33
186
  }
@@ -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
@@ -37,21 +37,28 @@ const NextResponse = require('next/server').NextResponse as typeof import('next/
37
37
 
38
38
  const logger = console;
39
39
 
40
- export function createAuthHandler(options = createNextAuthOptions()) {
41
- return NextAuth(options);
40
+ export function createAuthHandler(options: AuthHandlerOptions) {
41
+ return NextAuth(createNextAuthOptions(options));
42
42
  }
43
43
 
44
- export function createLogoutHandler(options = createNextAuthOptions()) {
44
+ const defaults = {
45
+ keycloak: {
46
+ baseUrl: 'http://localhost:8080',
47
+ realm: 'master',
48
+ },
49
+ };
50
+
51
+ export function createLogoutHandler(options: AuthHandlerOptions) {
45
52
  return {
46
53
  async GET() {
47
- const idToken = ((await getServerSession(options)) as Session)?.idToken;
54
+ const idToken = ((await getServerSession(createNextAuthOptions(options))) as Session)?.idToken;
48
55
  if (idToken) {
49
56
  try {
50
57
  await fetch(
51
- `${process.env.KEYCLOAK_BASE_URL}/realms/${
52
- process.env.KEYCLOAK_REALM || 'master'
58
+ `${options.keycloak?.baseUrl || defaults.keycloak.baseUrl}/realms/${
59
+ options.keycloak?.realm || defaults.keycloak.realm
53
60
  }/protocol/openid-connect/logout?id_token_hint=${idToken}&post_logout_redirect_uri=${encodeURIComponent(
54
- process.env.NEXTAUTH_URL || '',
61
+ options.baseUrl,
55
62
  )}`,
56
63
  { method: 'GET' },
57
64
  );
@@ -65,16 +72,32 @@ export function createLogoutHandler(options = createNextAuthOptions()) {
65
72
  };
66
73
  }
67
74
 
68
- export function createNextAuthOptions(options: CreateAuthHandlerOptions = {}) {
75
+ export function createNextAuthOptions(options: AuthHandlerOptions) {
69
76
  if (_nextAuth) return _nextAuth;
77
+ const authorization =
78
+ options.keycloak.authorization && typeof options.keycloak.authorization === 'object'
79
+ ? options.keycloak.authorization
80
+ : {};
70
81
  _nextAuth = {
71
82
  ...options.nextAuth,
72
83
  providers: [
73
84
  KeycloakProvider({
74
- clientId: process.env.KEYCLOAK_CLIENT_ID || '',
75
- clientSecret: process.env.KEYCLOAK_CLIENT_SECRET || '',
76
- issuer: `${process.env.KEYCLOAK_BASE_URL}/realms/${process.env.KEYCLOAK_REALM || 'master'}`,
77
- ...options.keycloakProvider,
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
+ },
100
+ ...options.keycloak,
78
101
  }),
79
102
  ...(options.nextAuth?.providers || []),
80
103
  ],
@@ -98,7 +121,7 @@ export function createNextAuthOptions(options: CreateAuthHandlerOptions = {}) {
98
121
  return token;
99
122
  } else {
100
123
  try {
101
- return refreshAccessToken(token as NextToken);
124
+ return refreshAccessToken(options, token as NextToken);
102
125
  } catch (err) {
103
126
  return { ...token, err };
104
127
  }
@@ -121,9 +144,18 @@ export function createNextAuthOptions(options: CreateAuthHandlerOptions = {}) {
121
144
  return _nextAuth;
122
145
  }
123
146
 
124
- export interface CreateAuthHandlerOptions {
125
- keycloakProvider?: Partial<OAuthUserConfig<any>>;
126
- nextAuth?: AuthOptions;
147
+ export interface AuthHandlerOptions {
148
+ baseUrl: string;
149
+ keycloak: KeycloakOptions;
150
+ nextAuth?: Partial<AuthOptions>;
151
+ scopes?: string[];
152
+ }
153
+
154
+ export interface KeycloakOptions extends OAuthUserConfig<any> {
155
+ adminPassword?: string;
156
+ adminUsername?: string;
157
+ baseUrl?: string;
158
+ realm?: string;
127
159
  }
128
160
 
129
161
  export interface NextToken extends JWT {
@@ -136,17 +168,19 @@ export interface NextToken extends JWT {
136
168
  roles: string[];
137
169
  }
138
170
 
139
- async function refreshAccessToken(nextToken: NextToken) {
171
+ async function refreshAccessToken(options: AuthHandlerOptions, nextToken: NextToken) {
140
172
  const res = await fetch(
141
- `${process.env.KEYCLOAK_BASE_URL}/realms/${process.env.KEYCLOAK_REALM || 'master'}/protocol/openid-connect/token`,
173
+ `${options.keycloak?.baseUrl || defaults.keycloak.baseUrl}/realms/${
174
+ options.keycloak?.realm || defaults.keycloak.realm
175
+ }/protocol/openid-connect/token`,
142
176
  {
143
177
  method: 'POST',
144
178
  headers: {
145
179
  'Content-Type': 'application/x-www-form-urlencoded',
146
180
  },
147
181
  body: new URLSearchParams({
148
- client_id: process.env.KEYCLOAK_CLIENT_ID || '',
149
- client_secret: process.env.KEYCLOAK_CLIENT_SECRET || '',
182
+ client_id: options.keycloak.clientId,
183
+ client_secret: options.keycloak.clientSecret,
150
184
  grant_type: 'refresh_token',
151
185
  refresh_token: nextToken.refreshToken,
152
186
  }),
@@ -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
- }