6ed3300183
Generated by create-expo-app 3.3.0.
177 lines
5.0 KiB
TypeScript
177 lines
5.0 KiB
TypeScript
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<any>(null);
|
|
|
|
export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
|
const [discovery, setDiscovery] =
|
|
useState<AuthSession.DiscoveryDocument | null>(null);
|
|
const [accessToken, setAccessToken] = useState<string | null>(null);
|
|
const [idToken, setIdToken] = useState<string | null>(null);
|
|
const [refreshToken, setRefreshToken] = useState<string | null>(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 <ActivityIndicator />;
|
|
|
|
return (
|
|
<AuthContext.Provider
|
|
value={{
|
|
accessToken,
|
|
idToken,
|
|
isLoaded,
|
|
refreshToken,
|
|
login,
|
|
logout,
|
|
refresh,
|
|
isAuthenticated: !!accessToken,
|
|
loading,
|
|
}}
|
|
>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
);
|
|
};
|
|
|
|
export const useAuth = () => useContext(AuthContext);
|