Skip to content

Commit 0537c7b

Browse files
committed
update auth to handle more reliable
1 parent 03810c1 commit 0537c7b

File tree

10 files changed

+176
-158
lines changed

10 files changed

+176
-158
lines changed

web/api/auth/authService.ts

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,74 @@
11
'use client'
22

33
import { getConfig } from '@/utils/config'
4-
54
import type { User } from 'oidc-client-ts'
6-
import { UserManager } from 'oidc-client-ts'
5+
import { UserManager, WebStorageStateStore } from 'oidc-client-ts'
76

87
const config = getConfig()
98

10-
11-
const userManager = new UserManager({
9+
const createUserManager = () => {
10+
return new UserManager({
1211
authority: config.auth.issuer,
1312
client_id: config.auth.clientId,
1413
redirect_uri: config.auth.redirect_uri,
1514
response_type: 'code',
1615
scope: 'openid profile email',
1716
post_logout_redirect_uri: config.auth.post_logout_redirect_uri,
18-
})
17+
userStore: typeof window !== 'undefined' ? new WebStorageStateStore({ store: window.localStorage }) : undefined,
18+
})
19+
}
20+
21+
export const userManager = createUserManager()
1922

2023
export const signUp = () => {
21-
return userManager.signinRedirect()
24+
return userManager.signinRedirect()
2225
}
2326

24-
export const login = (redirectURI?: string) => {
25-
return userManager.signinRedirect({ redirect_uri: redirectURI })
27+
export const login = async (returnUrl?: string) => {
28+
return userManager.signinRedirect({
29+
state: { returnUrl: returnUrl || window.location.href }
30+
})
2631
}
2732

2833
export const handleCallback = async () => {
29-
return await userManager.signinRedirectCallback()
34+
return await userManager.signinRedirectCallback()
3035
}
3136

3237
export const logout = () => {
33-
return userManager.signoutRedirect()
38+
return userManager.signoutRedirect()
3439
}
3540

3641
export const getUser = async (): Promise<User | null> => {
37-
return await userManager.getUser()
42+
return await userManager.getUser()
3843
}
3944

4045
export const renewToken = async () => {
41-
return await userManager.signinSilent()
46+
return await userManager.signinSilent()
4247
}
4348

4449
export const removeUser = async () => {
45-
return await userManager.removeUser()
50+
return await userManager.removeUser()
4651
}
4752

4853
export const restoreSession = async (): Promise<User | undefined> => {
49-
if (typeof window === 'undefined') return
54+
if (typeof window === 'undefined') return
55+
try {
5056
const user = await userManager.getUser()
51-
if (!user) return
57+
if (!user) return undefined
5258

5359
if (user.expired) {
54-
try {
55-
console.debug('Access token expired, refreshing...')
56-
const refreshedUser = await renewToken()
57-
return refreshedUser ?? undefined
58-
} catch (error) {
59-
console.debug('Silent token renewal failed', error)
60-
return
61-
}
60+
console.debug('Access token expired, refreshing...')
61+
const refreshedUser = await renewToken()
62+
return refreshedUser ?? undefined
6263
}
6364

6465
return user
66+
} catch (error) {
67+
console.warn('Session restoration failed:', error)
68+
return undefined
69+
}
6570
}
6671

6772
export const onTokenExpiringCallback = (callback: () => void) => {
68-
userManager.events.addAccessTokenExpiring(callback)
73+
userManager.events.addAccessTokenExpiring(callback)
6974
}

web/hooks/useAuth.tsx

Lines changed: 48 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
11
'use client'
22

33
import type { ComponentType, PropsWithChildren } from 'react'
4-
import { useEffect } from 'react'
5-
import { createContext, useContext, useState } from 'react'
4+
import { useEffect, createContext, useContext, useState } from 'react'
65
import { LoadingAnimation } from '@helpwave/hightide'
7-
import { LoginPage } from '@/components/pages/login'
86
import { login, logout, onTokenExpiringCallback, removeUser, renewToken, restoreSession } from '@/api/auth/authService'
97
import type { User } from 'oidc-client-ts'
10-
import { getConfig } from '@/utils/config'
11-
12-
const config = getConfig()
8+
import { usePathname } from 'next/navigation'
139

1410
type AuthContextType = {
1511
identity: User,
@@ -23,54 +19,64 @@ type AuthState = {
2319
isLoading: boolean,
2420
}
2521

26-
27-
// TODO add option for unprotected routes
2822
export const AuthProvider = ({ children }: PropsWithChildren) => {
29-
const [{ isLoading, identity }, setAuthState] = useState<AuthState>({ isLoading: true })
23+
const [authState, setAuthState] = useState<AuthState>({ isLoading: true })
24+
const pathname = usePathname()
3025

3126
useEffect(() => {
32-
restoreSession().then(identity => {
33-
if (identity) {
34-
setAuthState({
35-
identity,
36-
isLoading: false,
37-
})
38-
onTokenExpiringCallback(async () => {
39-
console.debug('Token expiring, refreshing...')
40-
const identity = await renewToken()
41-
setAuthState({
42-
identity: identity ?? undefined,
43-
isLoading: false,
44-
})
45-
})
46-
} else {
47-
login(config.auth.redirect_uri + `?redirect_uri=${encodeURIComponent(window.location.href)}`).catch(console.error)
27+
if (pathname === '/auth/callback') {
28+
setAuthState({ isLoading: false, identity: undefined })
29+
return
30+
}
31+
32+
let isMounted = true
33+
34+
const initAuth = async () => {
35+
try {
36+
const identity = await restoreSession()
37+
38+
if (isMounted) {
39+
if (identity) {
40+
setAuthState({ identity, isLoading: false })
41+
42+
onTokenExpiringCallback(async () => {
43+
const refreshedIdentity = await renewToken()
44+
setAuthState({
45+
identity: refreshedIdentity ?? undefined,
46+
isLoading: false,
47+
})
48+
})
49+
} else {
50+
await login()
51+
}
52+
}
53+
} catch {
54+
if (isMounted) {
55+
await removeUser()
56+
await login()
57+
}
4858
}
49-
}).catch(async () => {
50-
await removeUser()
51-
await login(config.auth.redirect_uri + `?redirect_uri=${encodeURIComponent(window.location.href)}`)
52-
})
53-
}, [])
59+
}
5460

55-
if (!identity && isLoading) {
56-
return (
57-
<div className="flex-col-0 items-center justify-center w-screen h-screen">
58-
<LoadingAnimation loadingText="Logging in..." />
59-
</div>
60-
)
61+
initAuth()
62+
63+
return () => { isMounted = false }
64+
}, [pathname])
65+
66+
if (pathname === '/auth/callback') {
67+
return <>{children}</>
6168
}
6269

63-
if (!identity) {
70+
if (authState.isLoading || !authState.identity) {
6471
return (
65-
<LoginPage login={async () => {
66-
await login(config.auth.redirect_uri + `?redirect_uri=${encodeURIComponent(window.location.href)}`)
67-
return true
68-
}} />
72+
<div className="flex flex-col items-center justify-center w-screen h-screen">
73+
<LoadingAnimation loadingText="Redirecting to login..." />
74+
</div>
6975
)
7076
}
7177

7278
return (
73-
<AuthContext.Provider value={{ identity, logout }}>
79+
<AuthContext.Provider value={{ identity: authState.identity, logout }}>
7480
{children}
7581
</AuthContext.Provider>
7682
)
@@ -87,7 +93,6 @@ export const withAuth = <P extends object>(Component: ComponentType<P>) => {
8793
return WrappedComponent
8894
}
8995

90-
9196
export const useAuth = () => {
9297
const context = useContext(AuthContext)
9398
if (!context) {

web/i18n/translations.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// AUTO-GENERATED. DO NOT EDIT.
22
/* eslint-disable @stylistic/quote-props */
3-
/* eslint-disable no-useless-escape */
4-
/* eslint-disable @typescript-eslint/no-unused-vars */
3+
4+
55
import type { Translation } from '@helpwave/internationalization'
66
import { TranslationGen } from '@helpwave/internationalization'
77

web/next-env.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
3-
import "./build/dev/types/routes.d.ts";
3+
import './build/dev/types/routes.d.ts'
44

55
// NOTE: This file should not be edited
66
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.

0 commit comments

Comments
 (0)