diff --git a/README.md b/README.md index eb65b7c..c384012 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,8 @@ type TAuthConfig = { // Optionally provide a callback function to run _after_ the // user has been redirected back from the auth server postLogin?: () => void // default: () => null + // Which method to use for login. Can be either 'redirect' or 'popup' + loginMethod: 'redirect' | 'popup' // default: 'redirect' // Optional callback function for the 'refreshTokenExpired' event. // You likely want to display a message saying the user need to log in again. A page refresh is enough. onRefreshTokenExpire?: (event: TRefreshTokenExpiredEvent) => void // default: undefined diff --git a/src/AuthContext.tsx b/src/AuthContext.tsx index 887541e..bebc464 100644 --- a/src/AuthContext.tsx +++ b/src/AuthContext.tsx @@ -133,10 +133,10 @@ export const AuthProvider = ({ authConfig, children }: IAuthProvider) => { function handleExpiredRefreshToken(initial = false): void { // If it's the first page load, OR there is no sessionExpire callback, we trigger a new login - if (initial) return logIn() + if (initial) return logIn(undefined, undefined, config.loginMethod) // TODO: Breaking change - remove automatic login during ongoing session - if (!config.onRefreshTokenExpire) return logIn() + if (!config.onRefreshTokenExpire) return logIn(undefined, undefined, config.loginMethod) config.onRefreshTokenExpire({ login: logIn, @@ -173,13 +173,13 @@ export const AuthProvider = ({ authConfig, children }: IAuthProvider) => { // Unknown error. Set error, and log in if first page load console.error(error) setError(error.message) - if (initial) logIn() + if (initial) logIn(undefined, undefined, config.loginMethod) } // Unknown error. Set error, and log in if first page load else if (error instanceof Error) { console.error(error) setError(error.message) - if (initial) logIn() + if (initial) logIn(undefined, undefined, config.loginMethod) } }) .finally(() => { @@ -255,7 +255,7 @@ export const AuthProvider = ({ authConfig, children }: IAuthProvider) => { } // First page visit - if (!token && config.autoLogin) return logIn() + if (!token && config.autoLogin) return logIn(undefined, undefined, config.loginMethod) refreshAccessToken(true) // Check if token should be updated }, []) diff --git a/src/authConfig.ts b/src/authConfig.ts index 1f3aa18..2e7121b 100644 --- a/src/authConfig.ts +++ b/src/authConfig.ts @@ -14,6 +14,7 @@ export function createInternalConfig(passedConfig: TAuthConfig): TInternalConfig scope = undefined, preLogin = () => null, postLogin = () => null, + loginMethod = 'redirect', onRefreshTokenExpire = undefined, storage = 'local', storageKeyPrefix = 'ROCP_', @@ -30,6 +31,7 @@ export function createInternalConfig(passedConfig: TAuthConfig): TInternalConfig scope: scope, preLogin: preLogin, postLogin: postLogin, + loginMethod: loginMethod, onRefreshTokenExpire: onRefreshTokenExpire, storage: storage, storageKeyPrefix: storageKeyPrefix, diff --git a/src/types.ts b/src/types.ts index 844b0ef..627beab 100644 --- a/src/types.ts +++ b/src/types.ts @@ -66,6 +66,7 @@ export type TAuthConfig = { logoutRedirect?: string preLogin?: () => void postLogin?: () => void + loginMethod: 'redirect' | 'popup' onRefreshTokenExpire?: (event: TRefreshTokenExpiredEvent) => void decodeToken?: boolean autoLogin?: boolean @@ -102,6 +103,7 @@ export type TInternalConfig = { logoutRedirect?: string preLogin?: () => void postLogin?: () => void + loginMethod: 'redirect' | 'popup' onRefreshTokenExpire?: (event: TRefreshTokenExpiredEvent) => void decodeToken: boolean autoLogin: boolean diff --git a/tests/auth-util.test.ts b/tests/auth-util.test.ts index d432826..14705ce 100644 --- a/tests/auth-util.test.ts +++ b/tests/auth-util.test.ts @@ -17,6 +17,7 @@ const authConfig: TInternalConfig = { refreshTokenExpiryStrategy: 'renewable', storageKeyPrefix: 'ROCP_', refreshWithScope: true, + loginMethod: 'redirect', extraAuthParams: { prompt: true, client_id: 'anotherClientId', diff --git a/tests/jestSetup.js b/tests/jestSetup.js index f3ac51a..19c6f9b 100644 --- a/tests/jestSetup.js +++ b/tests/jestSetup.js @@ -17,4 +17,5 @@ beforeEach(() => { const location = new URL('https://www.example.com') location.assign = jest.fn() window.location = location + window.open = jest.fn() }) diff --git a/tests/login.test.tsx b/tests/login.test.tsx index f680df3..15d189a 100644 --- a/tests/login.test.tsx +++ b/tests/login.test.tsx @@ -20,6 +20,24 @@ test('First page visit should redirect to auth provider for login', async () => }) }) +test('First page visit should popup to auth provider for login', async () => { + render( + + + + ) + + await waitFor(() => { + expect(window.open).toHaveBeenCalledWith( + expect.stringMatching( + /^myAuthEndpoint\?response_type=code&client_id=myClientID&redirect_uri=http%3A%2F%2Flocalhost%2F&code_challenge=.{43}&code_challenge_method=S256&scope=someScope\+openid&state=testState/gm + ), + 'loginPopup', + 'popup width=600 height=600' + ) + }) +}) + test('Attempting to log in with an unsecure context should raise error', async () => { // @ts-ignore window.crypto.subtle.digest = undefined diff --git a/tests/test-utils.tsx b/tests/test-utils.tsx index af204f7..8793938 100644 --- a/tests/test-utils.tsx +++ b/tests/test-utils.tsx @@ -12,6 +12,7 @@ export const authConfig: TAuthConfig = { scope: 'someScope openid', decodeToken: false, state: 'testState', + loginMethod: 'redirect', extraLogoutParameters: { testLogoutKey: 'logoutValue', },