(app-desk) integrate keycloak

Integrate keycloak with the frontend.
We use the keycloak-js library to handle the
authentication and authorization.
We installed Zustand to handle the states of the
application.
We store the token and auth process in authStore.
This commit is contained in:
Anthony LC
2024-01-09 17:01:08 +01:00
committed by Anthony LC
parent bf1b7736bb
commit d0b2f9c171
6 changed files with 111 additions and 2 deletions

View File

@@ -0,0 +1,3 @@
NEXT_PUBLIC_KEYCLOAK_URL=http://localhost:8080/
NEXT_PUBLIC_KEYCLOAK_REALM=people
NEXT_PUBLIC_KEYCLOAK_CLIENT_ID=people-front

View File

@@ -17,7 +17,8 @@
"@openfun/cunningham-react": "2.4.0",
"next": "14.0.4",
"react": "18.2.0",
"react-dom": "18.2.0"
"react-dom": "18.2.0",
"zustand": "4.4.7"
},
"devDependencies": {
"@testing-library/jest-dom": "6.2.0",
@@ -35,6 +36,7 @@
"eslint-plugin-testing-library": "6.2.0",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"keycloak-js": "23.0.3",
"prettier": "3.1.1",
"stylelint": "16.1.0",
"stylelint-config-standard": "36.0.0",

View File

@@ -1,10 +1,23 @@
'use client';
import { Button } from '@openfun/cunningham-react';
import { Button, Loader } from '@openfun/cunningham-react';
import { useEffect } from 'react';
import styles from './page.module.css';
import useAuthStore from '@/auth/useAuthStore';
export default function Home() {
const { initAuth, authenticated } = useAuthStore();
useEffect(() => {
initAuth();
}, [initAuth]);
if (!authenticated) {
return <Loader />;
}
return (
<main className={styles.main}>
<h2>Hello world!</h2>

View File

@@ -0,0 +1,23 @@
import Keycloak, { KeycloakConfig } from 'keycloak-js';
const keycloakConfig: KeycloakConfig = {
url: process.env.NEXT_PUBLIC_KEYCLOAK_URL,
realm: process.env.NEXT_PUBLIC_KEYCLOAK_REALM || '',
clientId: process.env.NEXT_PUBLIC_KEYCLOAK_CLIENT_ID || '',
};
export const initKeycloak = (onAuthSuccess: (_token?: string) => void) => {
const keycloak = new Keycloak(keycloakConfig);
keycloak
.init({
onLoad: 'login-required',
})
.then((authenticated) => {
if (authenticated) {
onAuthSuccess(keycloak.token);
}
})
.catch(() => {
throw new Error('Failed to initialize Keycloak.');
});
};

View File

@@ -0,0 +1,32 @@
import { create } from 'zustand';
import { initKeycloak } from './keycloak';
interface AuthStore {
initialized: boolean;
authenticated: boolean;
token: string | null;
initAuth: () => void;
}
const useAuthStore = create<AuthStore>((set) => ({
initialized: false,
authenticated: false,
token: null,
initAuth: () =>
set((state) => {
if (process.env.NODE_ENV === 'development' && !state.initialized) {
initKeycloak((token) => set({ authenticated: true, token }));
return { initialized: true };
}
/**
* TODO: Implement OIDC production authentication
*/
return {};
}),
}));
export default useAuthStore;

View File

@@ -2657,6 +2657,11 @@ balanced-match@^2.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-2.0.0.tgz#dc70f920d78db8b858535795867bf48f820633d9"
integrity sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==
base64-js@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -4752,6 +4757,11 @@ jest@29.7.0:
import-local "^3.0.2"
jest-cli "^29.7.0"
js-sha256@^0.10.1:
version "0.10.1"
resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.10.1.tgz#b40104ba1368e823fdd5f41b66b104b15a0da60d"
integrity sha512-5obBtsz9301ULlsgggLg542s/jqtddfOpV5KJc4hajc9JV8GeY2gZHSVpYBn4nWqAUTJ9v+xwtbJ1mIBgIH5Vw==
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -4856,6 +4866,20 @@ json5@^2.2.3:
object.assign "^4.1.4"
object.values "^1.1.6"
jwt-decode@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-4.0.0.tgz#2270352425fd413785b2faf11f6e755c5151bd4b"
integrity sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==
keycloak-js@23.0.3:
version "23.0.3"
resolved "https://registry.yarnpkg.com/keycloak-js/-/keycloak-js-23.0.3.tgz#91c71ac5c8e3bbafe9c7e5b8ef81756ffdd023ac"
integrity sha512-T28rdeRgGdOvIUMl6Wo9IiHEGcDxZhUZxXBwk3EwDFWrn8uhWBngDFxdsdsv7qGGKAMKrkZVL2JN/h81sllN7A==
dependencies:
base64-js "^1.5.1"
js-sha256 "^0.10.1"
jwt-decode "^4.0.0"
keyv@^4.5.3, keyv@^4.5.4:
version "4.5.4"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
@@ -6282,6 +6306,11 @@ url-parse@^1.5.3:
querystringify "^2.1.1"
requires-port "^1.0.0"
use-sync-external-store@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
util-deprecate@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
@@ -6513,3 +6542,10 @@ yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
zustand@4.4.7:
version "4.4.7"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.4.7.tgz#355406be6b11ab335f59a66d2cf9815e8f24038c"
integrity sha512-QFJWJMdlETcI69paJwhSMJz7PPWjVP8Sjhclxmxmxv/RYI7ZOvR5BHX+ktH0we9gTWQMxcne8q1OY8xxz604gw==
dependencies:
use-sync-external-store "1.2.0"