feat(auth): wire AuthProvider around app providers and mirror token to sessionStorage

This commit is contained in:
Urban Modig
2025-10-13 00:19:42 +02:00
parent 47cef8e5a5
commit 03201cf133
2 changed files with 79 additions and 1 deletions

73
auth/AuthProvider.tsx Normal file
View File

@ -0,0 +1,73 @@
import { createContext, useContext, useEffect, useMemo, useState, PropsWithChildren } from 'react'
import { userManager } from './oidc'
import type { User } from 'oidc-client-ts'
interface AuthCtx {
user: User | null
isAuthenticated: boolean
signIn: (returnTo?: string) => Promise<void>
signOut: () => Promise<void>
getAccessToken: () => string | null
}
const Ctx = createContext<AuthCtx | null>(null)
export function AuthProvider({ children }: PropsWithChildren) {
const [user, setUser] = useState<User | null>(null)
useEffect(() => {
userManager.getUser().then(u => setUser(u))
const onLoaded = (u: User) => setUser(u)
const onUnloaded = () => setUser(null)
const onExpired = async () => {
try { await userManager.signinSilent() } catch {/* ignore */}
}
userManager.events.addUserLoaded(onLoaded)
userManager.events.addUserUnloaded(onUnloaded)
userManager.events.addAccessTokenExpired(onExpired)
return () => {
userManager.events.removeUserLoaded(onLoaded)
userManager.events.removeUserUnloaded(onUnloaded)
userManager.events.removeAccessTokenExpired(onExpired)
}
}, [])
// Spegla token till sessionStorage så ky kan läsa den
useEffect(() => {
const token = user?.access_token ?? null
if (token) sessionStorage.setItem('access_token', token)
else sessionStorage.removeItem('access_token')
}, [user])
const api: AuthCtx = useMemo(() => ({
user,
isAuthenticated: !!user && !user.expired,
signIn: async (returnTo) => {
await userManager.signinRedirect({ state: { returnTo } })
},
signOut: async () => {
await userManager.signoutRedirect()
},
getAccessToken: () => user?.access_token ?? null,
}), [user])
return <Ctx.Provider value={api}>{children}</Ctx.Provider>
}
export function useAuth() {
const ctx = useContext(Ctx)
if (!ctx) throw new Error('useAuth must be used within <AuthProvider>')
return ctx
}

View File

@ -1,6 +1,7 @@
// src/app/providers.tsx
import { type PropsWithChildren, useState } from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { AuthProvider } from 'auth/AuthProvider'
export function AppProviders({ children }: PropsWithChildren) {
const [client] = useState(
@ -9,5 +10,9 @@ export function AppProviders({ children }: PropsWithChildren) {
defaultOptions: { queries: { refetchOnWindowFocus: false, retry: 1 } },
})
)
return <QueryClientProvider client={client}>{children}</QueryClientProvider>
return (
<AuthProvider>
<QueryClientProvider client={client}>{children}</QueryClientProvider>
</AuthProvider>
)
}