From 03201cf133b1daef00dcd38dadff7289cf84df8c Mon Sep 17 00:00:00 2001 From: Urban Modig Date: Mon, 13 Oct 2025 00:19:42 +0200 Subject: [PATCH] feat(auth): wire AuthProvider around app providers and mirror token to sessionStorage --- auth/AuthProvider.tsx | 73 +++++++++++++++++++++++++++++++++++++++++++ src/app/providers.tsx | 7 ++++- 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 auth/AuthProvider.tsx diff --git a/auth/AuthProvider.tsx b/auth/AuthProvider.tsx new file mode 100644 index 0000000..1a0700b --- /dev/null +++ b/auth/AuthProvider.tsx @@ -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 +signOut: () => Promise +getAccessToken: () => string | null +} + + +const Ctx = createContext(null) + + +export function AuthProvider({ children }: PropsWithChildren) { +const [user, setUser] = useState(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 {children} +} + + +export function useAuth() { +const ctx = useContext(Ctx) +if (!ctx) throw new Error('useAuth must be used within ') +return ctx +} \ No newline at end of file diff --git a/src/app/providers.tsx b/src/app/providers.tsx index 2884dd4..bb18377 100644 --- a/src/app/providers.tsx +++ b/src/app/providers.tsx @@ -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 {children} + return ( + + {children} + + ) }