[#1] Bare Skeleton with redux store, oidc and router-dom

This commit is contained in:
Camille Moussu
2025-06-25 17:42:54 +02:00
parent 57f9ba8427
commit cceb83519f
32 changed files with 21256 additions and 1 deletions
+65
View File
@@ -0,0 +1,65 @@
import React, { useEffect, useRef, useState } from "react";
import { useNavigate } from "react-router-dom";
import { Callback } from "./oidcAuth";
import { useAppDispatch, useAppSelector } from "../../app/hooks";
import { push } from "redux-first-history";
import { setUserData } from "./userSlice";
export function CallbackResume() {
const dispatch = useAppDispatch();
const hasRun = useRef(false);
const saved = sessionStorage.getItem("redirectState")
? JSON.parse(sessionStorage.getItem("redirectState")!)
: null;
const [tokens, setTokens] = useState<any>();
const [userInfo, setUserInfo] = useState<any>();
const location = useAppSelector((state) => state.router.location);
useEffect(() => {
if (hasRun.current) {
return;
}
hasRun.current = true;
const runCallback = async () => {
try {
const data = await Callback(saved?.code_verifier, saved?.state);
console.log("data:", data);
setTokens(data?.tokenSet);
setUserInfo(data?.userinfo);
dispatch(setUserData(data?.userinfo));
sessionStorage.removeItem("redirectState");
// Redirect to main page after successful callback
dispatch(push("/"));
} catch (e) {
console.error("OIDC callback error:", e);
}
};
if (saved?.code_verifier) {
runCallback();
} else {
console.warn("Missing redirectState");
sessionStorage.removeItem("redirectState");
dispatch(push("/"));
}
}, [dispatch, saved]);
return (
<div>
<p>Processing OIDC callback...</p>
{/* Optionally show loading or debug info */}
{tokens && (
<ul>
<li>
ID_Token: <pre>{JSON.stringify(tokens?.id_token)}</pre>
</li>
<li>
Access_Token: <pre>{JSON.stringify(tokens?.access_token)}</pre>
</li>
<li>
User info: <pre>{JSON.stringify(userInfo, null, 2)}</pre>
</li>
</ul>
)}
</div>
);
}
+87
View File
@@ -0,0 +1,87 @@
import * as client from "openid-client";
export const clientConfig = {
url: process.env.REACT_APP_SSO_BASE_URL || "",
client_id: process.env.REACT_APP_SSO_CLIENT_ID || "",
client_secret: process.env.REACT_APP_SSO_CLIENT_SECRET || "",
scope: process.env.REACT_APP_SSO_SCOPE || "",
redirect_uri: process.env.REACT_APP_SSO_REDIRECT_URI || "",
response_type: process.env.REACT_APP_SSO_RESPONSE_TYPE || "",
code_challenge_method: process.env.REACT_APP_SSO_CODE_CHALLENGE_METHOD || "",
post_logout_redirect_uri:
process.env.REACT_APP_SSO_POST_LOGOUT_REDIRECT || "",
};
export async function getClientConfig() {
console.log(process.env.REACT_APP_SSO_BASE_URL)
return await client.discovery(
new URL(clientConfig.url),
clientConfig.client_id,
clientConfig.client_secret
);
}
export async function Auth() {
console.log("bap");
let code_verifier = client.randomPKCECodeVerifier();
let code_challenge = await client.calculatePKCECodeChallenge(code_verifier);
const openIdClientConfig = await getClientConfig();
let parameters: Record<string, string> = {
redirect_uri: clientConfig.redirect_uri,
scope: clientConfig.scope!,
code_challenge,
code_challenge_method: clientConfig.code_challenge_method,
};
let state!: string;
if (!openIdClientConfig.serverMetadata().supportsPKCE()) {
state = client.randomState();
parameters.state = state;
}
let redirectTo = client.buildAuthorizationUrl(openIdClientConfig, parameters);
console.log(redirectTo);
return { redirectTo, code_verifier, state };
}
export async function Logout() {
const openIdClientConfig = await getClientConfig();
const endSessionUrl = client.buildEndSessionUrl(openIdClientConfig, {
post_logout_redirect_uri: clientConfig.post_logout_redirect_uri,
});
return endSessionUrl;
}
export async function Callback(code_verifier: string, state: any) {
try {
const openIdClientConfig = await getClientConfig();
const currentUrl = new URL(window.location.href);
console.log("Callback URL:", currentUrl.toString());
console.log("Code verifier:", code_verifier);
console.log("State:", state);
const tokenSet = await client.authorizationCodeGrant(
openIdClientConfig,
currentUrl,
{
pkceCodeVerifier: code_verifier,
expectedState: state,
}
);
const { access_token } = tokenSet;
const claims = tokenSet.claims()!;
const { sub } = claims;
const userinfo = await client.fetchUserInfo(
openIdClientConfig,
access_token,
sub
);
return { tokenSet, userinfo };
} catch (e) {
console.error("Token grant error:", e);
}
}
+5
View File
@@ -0,0 +1,5 @@
export interface userData {
email: string;
sid: string;
sub: string;
}
+19
View File
@@ -0,0 +1,19 @@
import { createSlice } from "@reduxjs/toolkit";
import { userData } from "./userDataTypes";
export const userSlice = createSlice({
name: "user",
initialState: {
userData: null as unknown as userData,
},
reducers: {
setUserData: (state, action) => {
state.userData = action.payload;
},
},
});
// Action creators are generated for each case reducer function
export const { setUserData } = userSlice.actions;
export default userSlice.reducer;