Skip to content

Commit b9635ff

Browse files
authored
Keycloak Auth in a reusable component (#118)
* Auth code * Added token context.
1 parent 3fae8b2 commit b9635ff

File tree

12 files changed

+859
-6
lines changed

12 files changed

+859
-6
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@diamondlightsource/sci-react-ui",
3-
"version": "0.3.1-alpha.1",
3+
"version": "0.3.1-alpha.3",
44
"files": [
55
"dist/"
66
],
@@ -27,6 +27,7 @@
2727
"storybook:publish": "gh-pages -b storybook/publish -d storybook-static"
2828
},
2929
"dependencies": {
30+
"keycloak-js": "^26.2.1",
3031
"react-icons": "^5.3.0",
3132
"utif": "^3.1.0"
3233
},

pnpm-lock.yaml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/navigation/Footer.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Meta, StoryObj } from "@storybook/react/*";
1+
import { Meta, StoryObj } from "@storybook/react";
22
import { Footer, FooterLink, FooterLinks } from "./Footer";
33
import { MockLink } from "../../utils/MockLink";
44

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { screen } from "@testing-library/react";
2+
3+
import { renderWithProviders } from "../../../__test-utils__/helpers";
4+
5+
import { AuthProvider } from "./AuthProvider";
6+
import { useAuth, useToken } from "./auth";
7+
8+
describe("AuthProvider useAuth", () => {
9+
const TestProvider = () => {
10+
const auth = useAuth();
11+
if (!auth.authenticated) return <>{"Not Authenticated"}</>;
12+
return <></>;
13+
};
14+
it("should be able to use useAuth", async () => {
15+
renderWithProviders(
16+
<AuthProvider
17+
keycloakConfig={{
18+
url: "_",
19+
realm: "_",
20+
clientId: "_",
21+
}}
22+
keycloakInitOptions={{
23+
onLoad: undefined,
24+
}}
25+
>
26+
<TestProvider />
27+
</AuthProvider>,
28+
);
29+
expect(await screen.findByText("Not Authenticated")).toBeInTheDocument();
30+
});
31+
32+
it("should be able to use useAuth with settings", async () => {
33+
renderWithProviders(
34+
<AuthProvider
35+
keycloakConfig={{
36+
url: "_",
37+
realm: "_",
38+
clientId: "_",
39+
}}
40+
keycloakInitOptions={{
41+
onLoad: undefined,
42+
}}
43+
onTokenChange={() => {}}
44+
minimumSecondsLeftInToken={15}
45+
>
46+
<TestProvider />
47+
</AuthProvider>,
48+
);
49+
expect(await screen.findByText("Not Authenticated")).toBeInTheDocument();
50+
});
51+
});
52+
53+
describe("AuthProvider useToken", () => {
54+
const TestProvider = () => {
55+
const token = useToken();
56+
return <div data-testid="token">{token}</div>;
57+
};
58+
59+
it("should be able to use useToken", async () => {
60+
renderWithProviders(
61+
<AuthProvider
62+
keycloakConfig={{
63+
url: "_",
64+
realm: "_",
65+
clientId: "_",
66+
}}
67+
keycloakInitOptions={{
68+
onLoad: undefined,
69+
}}
70+
>
71+
<TestProvider />
72+
</AuthProvider>,
73+
);
74+
expect(await screen.findByTestId("token")).toBeInTheDocument();
75+
});
76+
77+
it("should be able to use useAuth with settings", async () => {
78+
renderWithProviders(
79+
<AuthProvider
80+
keycloakConfig={{
81+
url: "_",
82+
realm: "_",
83+
clientId: "_",
84+
}}
85+
keycloakInitOptions={{
86+
onLoad: undefined,
87+
}}
88+
onTokenChange={() => {}}
89+
minimumSecondsLeftInToken={15}
90+
>
91+
<TestProvider />
92+
</AuthProvider>,
93+
);
94+
expect(await screen.findByTestId("token")).toBeInTheDocument();
95+
});
96+
});
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { PropsWithChildren, useEffect, useState } from "react";
2+
import Keycloak, {
3+
KeycloakServerConfig,
4+
KeycloakError,
5+
KeycloakInitOptions,
6+
} from "keycloak-js";
7+
import {
8+
AuthContext,
9+
AuthTokenContext,
10+
Auth,
11+
init,
12+
onAuthLogout,
13+
onAuthSuccess,
14+
onAuthRefreshSuccess,
15+
onError,
16+
updateAuth,
17+
} from "./auth";
18+
19+
export interface AuthProviderSettings {
20+
/* call function when token is set, renewed or cleared */
21+
onTokenChange?: (token: string) => void;
22+
/* Renew token before it expires by this amount, default 10s */
23+
minimumSecondsLeftInToken?: number;
24+
}
25+
26+
export interface AuthProviderProps
27+
extends AuthProviderSettings,
28+
PropsWithChildren {
29+
/* Main Keycloak.js config file. */
30+
keycloakConfig: KeycloakServerConfig;
31+
/* Keycloak.js initiate options. */
32+
keycloakInitOptions?: KeycloakInitOptions;
33+
}
34+
35+
export const AuthProvider = ({
36+
children,
37+
keycloakConfig,
38+
keycloakInitOptions,
39+
...settings
40+
}: AuthProviderProps) => {
41+
const [auth, setAuth] = useState<Auth>(updateAuth(null));
42+
const [token, setAuthToken] = useState<string | null>(null);
43+
44+
const keycloak = new Keycloak({ ...keycloakConfig });
45+
46+
const tokenChanged = (): void => {
47+
const token =
48+
keycloak.authenticated && keycloak.token ? keycloak.token : "";
49+
setAuthToken(token);
50+
if (settings.onTokenChange) settings.onTokenChange(token);
51+
};
52+
53+
keycloak.onAuthRefreshSuccess = () => {
54+
onAuthRefreshSuccess(keycloak, settings);
55+
tokenChanged();
56+
};
57+
keycloak.onAuthSuccess = () => {
58+
setAuth(onAuthSuccess(keycloak, settings));
59+
tokenChanged();
60+
};
61+
keycloak.onAuthLogout = () => {
62+
setAuth(onAuthLogout(keycloak, settings));
63+
tokenChanged();
64+
};
65+
66+
keycloak.onAuthError = (error: KeycloakError) => {
67+
const authChanged = onError(keycloak, "Auth error: " + error);
68+
if (authChanged) setAuth(authChanged);
69+
};
70+
71+
useEffect(() => {
72+
if (!keycloak.didInitialize) {
73+
init(keycloak, keycloakInitOptions).then((auth) => {
74+
if (auth) setAuth(auth);
75+
tokenChanged();
76+
});
77+
}
78+
}, []);
79+
80+
return (
81+
<AuthContext.Provider value={auth}>
82+
<AuthTokenContext.Provider value={token}>
83+
{children}
84+
</AuthTokenContext.Provider>
85+
</AuthContext.Provider>
86+
);
87+
};

0 commit comments

Comments
 (0)