Initial commit
Generated by create-expo-app 3.3.0.
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
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);
|
||||
Reference in New Issue
Block a user