@rcigroup/horizon-auth-sdk
v0.1.4
Published
Authentication library for RCI Group Horizon applications. Provides Supabase Azure SSO login, protected routes, role-based access control, and pre-built UI components.
Downloads
648
Readme
@rcigroup/horizon-auth-sdk
Authentication library for RCI Group Horizon applications. Provides Supabase Azure SSO login, protected routes, role-based access control, and pre-built UI components.
Installation
npm install @rcigroup/horizon-auth-sdkPeer Dependencies
These must be installed in your consuming project:
npm install @supabase/supabase-js react-router-dom @rcigroup/horizon-component-sdk react react-dom tailwindcss| Package | Version | Purpose |
|---|---|---|
| @supabase/supabase-js | >=2 | Supabase client for authentication |
| react-router-dom | >=6 | Routing (used by ProtectedRoute and RequireRole) |
| @rcigroup/horizon-component-sdk | >=0.1.0 | UI components (Button, Card, etc.) |
| react | >=18 | React |
| react-dom | >=18 | React DOM |
| tailwindcss | >=4 | Styling |
CSS Setup
In your main CSS file, add these lines so Tailwind picks up the auth SDK's class names and theme:
@import "tailwindcss";
@source "../node_modules/@rcigroup/horizon-auth-sdk/dist";
@import "@rcigroup/horizon-auth-sdk/styles.css";
@source "../node_modules/@rcigroup/horizon-component-sdk/dist";
@import "@rcigroup/horizon-component-sdk/styles.css";Quick Start
import { createClient } from "@supabase/supabase-js"
import { BrowserRouter, Routes, Route } from "react-router-dom"
import {
AuthProvider,
LoginPage,
LoginDisallowedPage,
ProtectedRoute,
RequireRole,
CurrentUser,
} from "@rcigroup/horizon-auth-sdk"
const supabase = createClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_ANON_KEY,
)
function App() {
return (
<BrowserRouter>
<AuthProvider config={{ appName: "My App", supabaseClient: supabase }}>
<Routes>
{/* Public routes */}
<Route path="/login" element={<LoginPage />} />
<Route path="/unauthorized" element={<LoginDisallowedPage />} />
{/* Protected routes — require authentication */}
<Route element={<ProtectedRoute />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
</Route>
{/* Role-protected routes */}
<Route element={<RequireRole roles="admin" />}>
<Route path="/admin" element={<AdminPanel />} />
</Route>
</Routes>
</AuthProvider>
</BrowserRouter>
)
}Components
<AuthProvider>
Root provider that initializes authentication and makes auth state available to all child components via React context. Must wrap your entire application (or at least the parts that use auth).
<AuthProvider config={config}>
{children}
</AuthProvider>Config (HorizonAuthConfig)
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
| appName | string | Yes | — | Application name displayed on the login page as "Welcome to {appName}" |
| supabaseClient | SupabaseClient | Yes | — | A configured Supabase client instance created via createClient() |
| redirectTo | string | No | window.location.origin | URL to redirect to after a successful OAuth login |
| supportContact | string | No | "Contact support to gain access." | Support contact text shown in the login page footer |
<LoginPage>
Full-page login screen with a centered card containing a welcome message, Microsoft SSO button, and support footer.
<LoginPage />
<LoginPage appName="Fleet Manager" supportContact="Email [email protected]" />Props (LoginPageProps)
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| appName | string | No | Value from AuthProvider config | Override the application name shown in the heading |
| supportContact | string | No | "Contact support to gain access." | Override the support text in the footer |
<LoginDisallowedPage>
Full-page "Access Denied" screen shown when an authenticated user lacks permission. Includes a sign-out button and support footer.
<LoginDisallowedPage />
<LoginDisallowedPage
appName="Fleet Manager"
message="Your account has not been provisioned."
supportContact="Email [email protected]"
/>Props (LoginDisallowedPageProps)
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| appName | string | No | Value from AuthProvider config | Override the application name in the denial message |
| message | string | No | "You do not have permission to access {appName}..." | Custom denial message |
| supportContact | string | No | "Contact support to gain access." | Override the support text in the footer |
<ProtectedRoute>
Route layout wrapper that requires authentication. Renders child routes when authenticated, redirects to the login page otherwise. Designed for use as a React Router layout route.
{/* As a layout route (recommended) */}
<Route element={<ProtectedRoute />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Route>
{/* With direct children */}
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
{/* Custom login path */}
<Route element={<ProtectedRoute loginPath="/auth/login" />}>
<Route path="/dashboard" element={<Dashboard />} />
</Route>Props (ProtectedRouteProps)
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| loginPath | string | No | "/login" | Path to redirect unauthenticated users to |
| loadingFallback | ReactNode | No | Built-in "Loading…" text | Custom component shown while auth state is loading |
| children | ReactNode | No | <Outlet /> | Content to render when authenticated. Uses React Router <Outlet /> if omitted |
<RequireRole>
Route layout wrapper that requires the user to have specific role(s). Checks authentication first, then validates roles. Designed for use as a React Router layout route.
Roles are read from user.app_metadata.roles by default. You can provide a custom getRoles function to change this behavior.
{/* Single role */}
<Route element={<RequireRole roles="admin" />}>
<Route path="/admin" element={<AdminPanel />} />
</Route>
{/* Multiple roles (user needs at least one) */}
<Route element={<RequireRole roles={["admin", "manager"]} />}>
<Route path="/admin" element={<AdminPanel />} />
</Route>
{/* Custom role extraction */}
<Route element={
<RequireRole
roles="editor"
getRoles={(user) => user.app_metadata?.custom_roles as string[]}
/>
}>
<Route path="/editor" element={<Editor />} />
</Route>Props (RequireRoleProps)
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| roles | string \| string[] | Yes | — | Role(s) required. If an array, the user must have at least one |
| unauthorizedPath | string | No | "/unauthorized" | Path to redirect users who lack the required role |
| getRoles | (user) => string[] | No | Reads user.app_metadata.roles | Custom function to extract roles from the Supabase user object |
| loadingFallback | ReactNode | No | Built-in "Loading…" text | Custom component shown while auth state is loading |
| children | ReactNode | No | <Outlet /> | Content to render when authorized. Uses React Router <Outlet /> if omitted |
<SignOutButton>
A pre-wired button that triggers sign-out. Accepts all Button props from @rcigroup/horizon-component-sdk (variant, size, className, etc.). Automatically disables itself and shows "Signing out…" while the request is in progress.
<SignOutButton />
<SignOutButton variant="outline" size="sm" />
<SignOutButton label="Log out" variant="ghost" />
<SignOutButton variant="destructive">Leave application</SignOutButton>Props (SignOutButtonProps)
Inherits all props from Button except onClick.
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| label | string | No | "Sign Out" | Button text. Ignored if children is provided |
| variant | string | No | "default" | Button style variant ("default", "outline", "ghost", "destructive") |
| size | string | No | "default" | Button size ("default", "sm", "lg", "icon") |
| children | ReactNode | No | — | Custom button content (overrides label) |
| disabled | boolean | No | false | Disable the button |
| className | string | No | — | Additional CSS classes |
<CurrentUser>
Adaptive component that displays the current user's identity when logged in, or a sign-in button when logged out. Useful for headers, navbars, and toolbars.
{/* Default: show email + sign-out button */}
<CurrentUser />
{/* Show full name, hide sign-out */}
<CurrentUser display="fullName" showSignOut={false} />
{/* Show first name with custom labels */}
<CurrentUser display="firstName" signInLabel="Log in" signOutLabel="Log out" />Props (CurrentUserProps)
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| display | "email" \| "firstName" \| "fullName" | No | "email" | What user info to display when logged in |
| showSignOut | boolean | No | true | Whether to show a sign-out button next to the user info |
| signInLabel | string | No | "Sign in with Microsoft" | Text for the sign-in button shown when logged out |
| signOutLabel | string | No | "Sign Out" | Text for the sign-out button |
The display options:
"email"— Shows the user's email address (e.g.[email protected])"firstName"— Shows the first name fromuser_metadata.full_name(e.g.Jane)"fullName"— Shows the full name fromuser_metadata.full_name(e.g.Jane Doe)
Hooks
useAuth()
Returns the full auth context. Must be called inside an <AuthProvider>.
const { user, session, isAuthenticated, isLoading, signIn, signOut, appName, supabaseClient } = useAuth()| Property | Type | Description |
|---|---|---|
| user | User \| null | Current Supabase user object |
| session | Session \| null | Current Supabase session (contains access_token, refresh_token) |
| isAuthenticated | boolean | true when a valid session exists |
| isLoading | boolean | true while the initial session is being loaded |
| signIn | () => Promise<void> | Triggers Azure SSO sign-in (redirects the browser) |
| signOut | () => Promise<void> | Signs out the current user |
| appName | string | Application name from config |
| supabaseClient | SupabaseClient | The Supabase client instance |
useUser()
Returns the current Supabase User object, or null if not authenticated.
const user = useUser()
if (user) console.log(user.email)useSession()
Returns the current Supabase Session object, or null. The session contains the JWT access token.
const session = useSession()
if (session) {
fetch("/api/data", {
headers: { Authorization: `Bearer ${session.access_token}` },
})
}useAuthLoading()
Returns true while the initial auth state is being loaded from Supabase. Use this to display loading indicators.
const loading = useAuthLoading()
if (loading) return <Spinner />useHasRole(roles, getRoles?)
Checks if the current user has at least one of the specified roles.
const isAdmin = useHasRole("admin")
const canEdit = useHasRole(["admin", "editor"])
// Custom role extraction:
const isSuperUser = useHasRole("super", (user) => user.app_metadata?.custom_roles as string[])| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| roles | string \| string[] | Yes | — | Role(s) to check. Returns true if the user has at least one |
| getRoles | (user: User) => string[] | No | Reads user.app_metadata.roles | Custom function to extract roles |
Exported Types
| Type | Description |
|---|---|
| HorizonAuthConfig | Configuration object for <AuthProvider> |
| AuthState | Auth state shape: session, user, isLoading, isAuthenticated |
| AuthContextValue | Full context value returned by useAuth() |
| LoginPageProps | Props for <LoginPage> |
| LoginDisallowedPageProps | Props for <LoginDisallowedPage> |
| ProtectedRouteProps | Props for <ProtectedRoute> |
| RequireRoleProps | Props for <RequireRole> |
| SignOutButtonProps | Props for <SignOutButton> |
| CurrentUserProps | Props for <CurrentUser> |
| CurrentUserDisplay | Union type: "email" \| "firstName" \| "fullName" |
| Session | Re-exported from @supabase/supabase-js |
| User | Re-exported from @supabase/supabase-js |
| SupabaseClient | Re-exported from @supabase/supabase-js |
Supabase Setup
This library uses Supabase Azure SSO (OAuth with the azure provider). To configure:
- Supabase Dashboard → Authentication → Providers → Enable Azure
- Azure AD → App registrations → Create a new app → Set the redirect URI to your Supabase project's callback URL (
https://<project-ref>.supabase.co/auth/v1/callback) - Role-based access → Set user roles in
app_metadata.roles(e.g.["admin", "editor"]) via the Supabase dashboard or a server-side function
Development
npm run dev # Run the dev app (Vite)
npm run storybook # Run Storybook for component previews
npm run build # Build the library
npm run typecheck # Type-check
npm run test # Run tests