
Security News
Risky Biz Podcast: Making Reachability Analysis Work in Real-World Codebases
This episode explores the hard problem of reachability analysis, from static analysis limits to handling dynamic languages and massive dependency trees.
@bcgov/citz-imb-kc-react
Advanced tools
BCGov Pathfinder SSO Keycloak integration for React
/api
.backendURL
./api
.backendURL
.Run npm install @bcgov/citz-imb-kc-react
or select a specific version tag from NPM Package.
import { KeycloakProvider } from "@bcgov/citz-imb-kc-react";
and surround your application code with KeycloakProvider
.Example:
import { KeycloakProvider } from "@bcgov/citz-imb-kc-react";
import App from "./App";
import React from "react";
import { createRoot } from "react-dom/client";
const root = createRoot(document.getElementById("root") as HTMLElement);
root.render(
<React.StrictMode>
<KeycloakProvider idpHint="idir">
<App />
</KeycloakProvider>
</React.StrictMode>
);
[!NOTE] KeycloakProvider has optional props 'backendURL', 'idpHint', and 'onRefreshExpiry'.
Add import import { useKeycloak } from "@bcgov/citz-imb-kc-react";
then add the following to the top of your functional component:
const {
isAuthenticated,
login,
logout,
} = useKeycloak();
Conditionally render a Login or Logout button:
{isAuthenticated ? (
<button onClick={() => logout()}>
LOGOUT
</button>
) : (
<button onClick={() => login({ idpHint: "idir" })}>
LOGIN WITH IDIR
</button>
)}
To access auth state and functions add import import { useKeycloak } from "@bcgov/citz-imb-kc-react";
then add the following to the top of your functional component:
const {
user,
hasRole,
getAuthorizationHeaderValue,
isAuthenticated,
} = useKeycloak();
// Is user logged in:
if (isAuthenticated) console.log(`Hi ${user?.display_name}`);
Check if the user has a role like:
// User must have 'Admin' role.
if (hasRole(['Admin'])) // Do something...
// Users must have BOTH 'Member' and 'Commenter' roles.
// requireAllRoles option is true by default.
if (hasRole(['Member', 'Commenter'])) // Do something...
// Users must have EITHER 'Member' or 'Verified' role.
if (hasRole(['Member', 'Verified'], { requireAllRoles: false })) // Do Something...
Complete an authenticated request like in the example below:
Both functions come from useKeycloak
.
// NEW way:
// Using fetchProtectedRoute, which is a wrapper for Node Fetch API.
const callTest = async () => {
const response = await fetchProtectedRoute("/api/test", {
method: "GET"
});
return await response.json();
};
// OLD way:
// Using getAuthorizationHeaderValue function.
const callTest = async () => {
const response = await fetch("/api/test", {
method: "GET",
headers: {
Authorization: getAuthorizationHeaderValue(),
},
});
return await response.json();
};
For all user properties reference SSO Keycloak Wiki - Identity Provider Attribute Mapping.
Example IDIR user
object (Typescript Type is KeycloakUser & KeycloakIdirUser
):
{
"idir_user_guid": "W7802F34D2390EFA9E7JK15923770279",
"identity_provider": "idir",
"idir_username": "JOHNDOE",
"name": "Doe, John CITZ:EX",
"preferred_username": "a7254c34i2755fea9e7ed15918356158@idir",
"given_name": "John",
"display_name": "Doe, John CITZ:EX",
"family_name": "Doe",
"email": "john.doe@gov.bc.ca",
"client_roles": ["Admin"]
}
[!Note*] 'client_roles' is the only property in this list that can be
undefined
. All other properties if empty will be an empty string. When checking if a user has a role, it is advised to use the hasRole() function from useKeycloak().
For use with vite, the following setup as a property of server
inside your vite config will work:
proxy: {
"/api": {
target: "http://<backend-service-name>:<backend-port>/",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
Be sure to replace <backend-service-name>
and <backend-port>
. For docker containers, this is the service name from the compose file.
For use in Nginx configs, the following setup as a property of server
will work:
location /api/ {
proxy_pass http://<backend-service-name>:<backend-port>/;
proxy_set_header Host $host;
}
Again, be sure to replace <backend-service-name>
and <backend-port>
.
backendUrl
and idpHint
props on KeycloakProvider
component and login
function.Even if you are using a proxy, idpHint
is always recommended.
// Example usage:
<KeycloakProvider backendURL="http://localhost:3000" idpHint="idir">
</KeycloakProvider>
// Example usage:
login({ backendURL: "http://localhost:3000", idpHint: "idir" });
BY DEFUALT, when a refresh token expires, the user will be prompted to re-login by the RefreshExpiryDialog. This can be swapped out for a custom solution by adding a onRefreshExpiry
prop to KeycloakProvider
.
Example:
import { KeycloakProvider } from "@bcgov/citz-imb-kc-react";
import App from "./App";
import React from "react";
import { createRoot } from "react-dom/client";
const customTokenExpiry = () => {
// Do something such as prompt user to login again.
};
const root = createRoot(document.getElementById("root") as HTMLElement);
root.render(
<React.StrictMode>
<KeycloakProvider onRefreshExpiry={() => customTokenExpiry()}>
<App />
</KeycloakProvider>
</React.StrictMode>
);
Here's how the built in RefreshExpiryDialog
works:
Within KeycloakProvider
is the following functionality:
// State to track if the dialog should be visible.
const [isExpiryDialogVisible, setIsExpiryDialogVisible] = useState(false);
// Set State to be called onRefreshExpiry to make the dialog visible.
setIsExpiryDialogVisible(true)
// The dialog component.
<RefreshExpiryDialog
loginProps={{ backendURL, idpHint }}
isVisible={isExpiryDialogVisible}
/>
Here is the RefreshExpiryDialog
component:
const RefreshExpiryDialog = (props: RefreshExpiryDialogProps) => {
const { isVisible, loginProps } = props;
const { login } = useKeycloak();
if (!isVisible) return null;
return (
<>
<div className="kcr_dialog-overlay" />
<dialog className="kcr_dialog" open={isVisible}>
<div className="kcr_dialog-content">
<p className="kcr_dialog-message">Your login session has expired.</p>
<button className="kcr_button" onClick={() => login(loginProps)}>
RE-LOGIN
</button>
</div>
</dialog>
</>
);
};
Here is the RefreshExpiryDialog
css classes:
.kcr_dialog-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5); /* Translucent grey */
z-index: 999; /* Ensure it's below the dialog and above other elements */
}
.kcr_dialog {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border: 1px solid #484848;
border-radius: 5px;
padding: 40px;
background-color: #fff;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
z-index: 1000;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.kcr_dialog-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.kcr_dialog-message {
font-size: 1.25em;
font-weight: 700;
color: #656565;
font-family: 'Trebuchet MS', Arial, sans-serif;
text-align: center;
margin-bottom: 20px;
}
.kcr_button {
background-color: #234075;
color: white;
border: none;
padding: 10px 20px;
border: 1px solid #484848;
border-radius: 5px;
font-size: 1em;
font-family: 'Trebuchet MS', Arial, sans-serif;
cursor: pointer;
transition: background-color 0.3s ease;
}
.kcr_button:hover {
background-color: #1b325c;
}
.
├── .github/
| ├── config/
| | └── dep-report.json5 # Configure options for NPM Dep Report.
| ├── helpers/
| | ├── github-api/ # Functions to access the GitHub API.
| | ├── create-npm-dep-report-issues.js # Creates GitHub Issues for Npm Dep Reports.
| | ├── create-npm-dep-report.js # Creates text bodies for Npm Dep Reports.
| | ├── parse-json5-config.js # Parses json5 files for GitHub actions output.
| | └── parse-npm-deps.js # Parses package.json files for changes to package versions.
| ├── workflows/
| | ├── npm-dep-report.yaml # Reports on new package versions.
| | └── releases.yaml # Creates a new GitHub Release.
├── .husky/
| └── post-commit # Script that runs after a git commit.
├── scripts/
| ├── bump-version.mjs # Bumps version in package.json file.
| ├── post-commit-version-change.mjs # Bumps version when post-commit is run.
| ├── remove-css-imports.js # Removes css imports from TypeScript declaration files from the build.
| ├── remove-dts-files.js # Removes TypeScript declaration files from the build.
| └── remove-empty-dirs.js # Removes empty directories from the build.
├── src/ # Source code for package.
| ├── components/
| | ├── Provider.tsx # Provides auth state to an application.
| | ├── RefreshExpiryDialog.tsx # Default dialog to come up when token expires.
| | └── Wrapper.tsx # Provides auth services such as refresh token tracking.
| ├── state/
| | ├── reducer.ts # Manages auth state from context.
| | └── useKeycloak.ts # Functions using auth state.
| ├── context.ts # React Context for storing auth data.
| ├── index.ts # Export functions for the package.
| ├── types.ts # TypeScript types.
| └── utils.ts # Utility functions.
├── package.json # Package config and dependencies.
├── .npmrc # NPM config.
├── rollup.config.js # Builds and compiles TypeScript files into JavaScript.
├── rollupdts.config.js # Builds and compiles TypeScript declartion files.
# Compile all src code into a bundle in build/ directory.
$ npm run build
# Part of 'build' and it bundles the typescipt declarations into a single bundle.d.ts file.
$ npm run build:dts
# Part of build and it removes directories and files before the build.
$ npm run clean:prebuild
# Part of build and it removes directories and files after the build.
$ npm run clean:postbuild
# Used to package the code before a release.
$ npm run pack
These are the functions and types exported by the @bcgov/citz-imb-kc-react
module.
import {
KeycloakProvider, // Manages the keycloak service in your react app.
useKeycloak, // Hook used for authentication and authorization state and functions.
} from '@bcgov/citz-imb-kc-react';
// TypeScript Types:
import {
KeycloakProviderProps, // Props for KeycloakProvider component.
LoginProps, // Props for login function of useKeycloak().
IdirIdentityProvider, // Used for more efficient login.
GithubIdentityProvider, // Used for more efficient login.
BceidIdentityProvider, // Used for more efficient login.
IdentityProvider, // Combined type for identity providers.
HasRoleOptions, // Optional options parameter for hasRole function of useKeycloak().
AuthService, // Type for useKeycloak().
AuthState, // Type for state of useKeycloak().
KeycloakUser, // Base user type.
KeycloakIdirUser, // User types specific to Idir users.
KeycloakBCeIDUser, // User types specific to BCeID users.
KeycloakGithubUser, // User types specific to Github users.
} from '@bcgov/citz-imb-kc-react';
These are the TypeScript types of the @bcgov/citz-imb-kc-react
module.
const reducer = (state: AuthState, action: AuthAction): AuthState;
const useKeycloak = (): AuthService;
const KeycloakProvider = (props: KeycloakProviderProps): React.JSX.Element;
// Defines the possible types of authentication actions.
export enum AuthActionType {
LOGOUT = 'LOGOUT',
ATTEMPT_LOGIN = 'ATTEMPT_LOGIN',
REFRESH_TOKEN = 'REFRESH_TOKEN',
UNAUTHORIZED = 'UNAUTHORIZED',
}
// Initial authentication state.
export const initialState: AuthState = {
isLoggingIn: false,
isAuthenticated: false,
accessToken: undefined,
idToken: undefined,
userInfo: undefined,
};
// PROPS
export type KeycloakProviderProps = {
backendURL?: string;
idpHint?: IdentityProvider;
children: ReactNode;
onRefreshExpiry?: Function;
};
export type KeycloakWrapperProps = {
backendURL?: string;
children: ReactNode;
onRefreshExpiry?: Function;
};
export type LoginProps = {
backendURL?: string;
idpHint?: IdentityProvider;
};
export type RefreshExpiryDialogProps = {
isVisible: boolean;
loginProps?: LoginProps;
};
export type IdirIdentityProvider = "idir";
export type GithubIdentityProvider = "githubbcgov" | "githubpublic";
export type BceidIdentityProvider =
| "bceidbasic"
| "bceidbusiness"
| "bceidboth";
export type IdentityProvider =
| IdirIdentityProvider
| BceidIdentityProvider
| GithubIdentityProvider;
export type HasRoleOptions = {
requireAllRoles?: boolean;
};
export type AuthService = {
state: AuthState;
isAuthenticated: boolean;
isLoggingIn: boolean;
user?: KeycloakUser;
getAuthorizationHeaderValue: () => string;
fetchProtectedRoute: (url: string, options?: any) => Promise<Response>;
hasRole: (roles: string[], options?: HasRoleOptions) => boolean;
refreshToken: (backendURL?: string) => Promise<void>;
login: (options?: LoginProps) => void;
logout: (backendURL?: string) => void;
};
export type AuthAction = {
type: AuthActionType;
payload?: {
accessToken?: string;
idToken?: string;
userInfo?: KeycloakUser;
};
};
export type AuthState = {
isLoggedIn: boolean;
isAuthenticated: boolean;
accessToken?: string;
idToken?: string;
userInfo?: KeycloakUser;
};
export type AuthStateWithDispatch = {
state: AuthState;
dispatch: Dispatch<AuthAction>;
};
export type BaseKeycloakUser = {
name?: string;
preferred_username: string;
email: string;
display_name: string;
client_roles?: string[];
scope?: string;
identity_provider:
| IdirIdentityProvider
| BceidIdentityProvider
| GithubIdentityProvider;
};
export type KeycloakIdirUser = {
idir_user_guid?: string;
idir_username?: string;
given_name?: string;
family_name?: string;
};
export type KeycloakBCeIDUser = {
bceid_user_guid?: string;
bceid_username?: string;
bceid_business_name?: string;
};
export type KeycloakGithubUser = {
github_id?: string;
github_username?: string;
orgs?: string;
given_name?: string;
family_name?: string;
first_name?: string;
last_name?: string;
};
export type KeycloakUser = BaseKeycloakUser &
KeycloakIdirUser &
KeycloakBCeIDUser &
KeycloakGithubUser;
The following applications are currently using this keycloak implementation solution:
SET - Salary Estimation Tool PLAY - CITZ IMB Package Testing App
FAQs
BCGov Pathfinder SSO Keycloak integration for React
The npm package @bcgov/citz-imb-kc-react receives a total of 0 weekly downloads. As such, @bcgov/citz-imb-kc-react popularity was classified as not popular.
We found that @bcgov/citz-imb-kc-react demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 19 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
This episode explores the hard problem of reachability analysis, from static analysis limits to handling dynamic languages and massive dependency trees.
Security News
/Research
Malicious Nx npm versions stole secrets and wallet info using AI CLI tools; Socket’s AI scanner detected the supply chain attack and flagged the malware.
Security News
CISA’s 2025 draft SBOM guidance adds new fields like hashes, licenses, and tool metadata to make software inventories more actionable.