import React, { createContext, useContext, useEffect, useState } from "react"; import * as AuthSession from "expo-auth-session"; import * as WebBrowser from "expo-web-browser"; import { Platform } from "react-native"; import { Storage } from "./storage"; import { keycloakConfig } from "./keycloakConfig"; import { ActivityIndicator } from "react-native"; // Only call maybeCompleteAuthSession on web platform if (Platform.OS === "web") { WebBrowser.maybeCompleteAuthSession(); } const TOKEN_KEY = "kc_access_token"; const REFRESH_KEY = "kc_refresh_token"; const ID_TOKEN_KEY = "kc_id_token"; const AuthContext = createContext(null); export const AuthProvider = ({ children }: { children: React.ReactNode }) => { const [discovery, setDiscovery] = useState(null); const [accessToken, setAccessToken] = useState(null); const [idToken, setIdToken] = useState(null); const [refreshToken, setRefreshToken] = useState(null); const [loading, setLoading] = useState(true); const [isLoaded, setIsLoaded] = useState(false); const redirectUri = AuthSession.makeRedirectUri({ scheme: keycloakConfig.scheme, path: Platform.OS === "web" ? "" : "(auth)/index", }); useEffect(() => { const loadDiscovery = async () => { const doc = await AuthSession.fetchDiscoveryAsync( `${keycloakConfig.url}/realms/${keycloakConfig.realm}` ); setDiscovery(doc); }; loadDiscovery(); }, []); useEffect(() => { const restore = async () => { const storedAccess = await Storage.getItem(TOKEN_KEY); const storedId = await Storage.getItem(ID_TOKEN_KEY); const storedRefresh = await Storage.getItem(REFRESH_KEY); if (storedAccess && storedRefresh) { setAccessToken(storedAccess); setIdToken(storedId); setRefreshToken(storedRefresh); } setLoading(false); setIsLoaded(true); }; restore(); }, []); const login = async () => { if (!discovery) return; const authRequest = new AuthSession.AuthRequest({ responseType: AuthSession.ResponseType.Code, clientId: keycloakConfig.clientId, redirectUri, scopes: ["openid", "profile", "email", "offline_access"], usePKCE: true, prompt: AuthSession.Prompt.Login, }); await authRequest.makeAuthUrlAsync(discovery); const result = await authRequest.promptAsync(discovery); if (result.type === "success") { const tokenResponse = await AuthSession.exchangeCodeAsync( { clientId: keycloakConfig.clientId, code: result.params.code, redirectUri, extraParams: { code_verifier: authRequest.codeVerifier || "", }, }, discovery ); const { accessToken, idToken, refreshToken } = tokenResponse; setAccessToken(accessToken); setIdToken(idToken ?? null); setRefreshToken(refreshToken ?? null); await Storage.setItem(TOKEN_KEY, accessToken || ""); if (idToken) await Storage.setItem(ID_TOKEN_KEY, idToken); if (refreshToken) await Storage.setItem(REFRESH_KEY, refreshToken); } }; const refresh = async () => { if (!discovery || !refreshToken) return; const newToken = await AuthSession.refreshAsync( { clientId: keycloakConfig.clientId, refreshToken, }, discovery ); setAccessToken(newToken.accessToken || null); setIdToken(newToken.idToken || null); setRefreshToken(newToken.refreshToken || null); await Storage.setItem(TOKEN_KEY, newToken.accessToken || ""); if (newToken.idToken) await Storage.setItem(ID_TOKEN_KEY, newToken.idToken); if (newToken.refreshToken) await Storage.setItem(REFRESH_KEY, newToken.refreshToken); }; const logout = async () => { if (!discovery || !accessToken) return; const clientId = keycloakConfig.clientId; const logOutRedirectUri = AuthSession.makeRedirectUri({ scheme: keycloakConfig.scheme, path: Platform.OS === "web" ? "" : "home", }); const logoutUrl = `${ discovery.endSessionEndpoint }?client_id=${clientId}&post_logout_redirect_uri=${encodeURIComponent( logOutRedirectUri )}&id_token_hint=${idToken}`; const result = await WebBrowser.openAuthSessionAsync( logoutUrl, logOutRedirectUri ); if (result.type === "success") { setAccessToken(null); setIdToken(null); setRefreshToken(null); await Storage.deleteItem(TOKEN_KEY); await Storage.deleteItem(ID_TOKEN_KEY); await Storage.deleteItem(REFRESH_KEY); } }; if (!discovery || loading) return ; return ( {children} ); }; export const useAuth = () => useContext(AuthContext);