@massimo.mazzoleni/ui-cognito-max
v2.5.1
Published
Ready-to-use Cognito auth pages for React + AWS Cloudscape
Downloads
1,120
Readme
ui-cognito-max
Ready-to-use Cognito auth pages for React + AWS Cloudscape Design System.
INSTALLAZIONE
Dopo aver clonato il repo da Bitbucket, lanciare dalla root lo script install-all.sh
per eseguire npm install nella root e in tutte le app di apps/:
./install-all.shInstallation
npm install @massimo.mazzoleni/ui-cognito-max @cloudscape-design/components react-router-dom react-hook-form qrcode.reactqrcode.react renders the scannable QR code shown in the MFA setup page (MfaSetupPage).
Package layout
A single package, four entry points — pick the one that matches how much you need:
| Subpath | What it exposes | Extra peer deps needed |
|---|---|---|
| @massimo.mazzoleni/ui-cognito-max | <CognitoAuthRoutes> + ready-made Cloudscape pages (this doc) | @cloudscape-design/components, react-router-dom, react-hook-form, qrcode.react |
| @massimo.mazzoleni/ui-cognito-max/react | Headless hooks (useAuth, useSession, useUser, useMfa, useDevice, usePasswordPolicy), <AuthProvider>, <ProtectedRoute> | none beyond react |
| @massimo.mazzoleni/ui-cognito-max/core | Framework-agnostic Cognito client (CognitoAuthClient, AuthConfig, storage adapters, errors) | none — usable outside React, even server-side |
| @massimo.mazzoleni/ui-cognito-max/headless | Unstyled form components (SignInForm, RegisterForm, ...) + their hooks, for building a fully custom UI | none beyond react |
Only react/react-dom are required peers; everything else is optional in
peerDependenciesMeta — if you only import /core or /react, you don't need to install
Cloudscape/react-router-dom/react-hook-form/qrcode.react at all.
Usage
// App.tsx
import { BrowserRouter, Route } from 'react-router-dom'
import { CognitoAuthRoutes } from '@massimo.mazzoleni/ui-cognito-max'
function App() {
return (
<BrowserRouter>
<CognitoAuthRoutes
config={{ userPoolId: 'eu-south-1_xxx', clientId: 'xxx', region: 'eu-south-1' }}
logo={{ src: '/logo.png', alt: 'Logo', height: 80 }}
appTitle="Il Mio Applicativo"
lang="it"
>
<Route path="/" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</CognitoAuthRoutes>
</BrowserRouter>
)
}Auth routes included
| Path | Component | Protected |
|------|-----------|-----------|
| /login | LoginPage | No (redirect if authenticated) |
| /register | RegisterPage | No |
| /confirm-signup | ConfirmSignupPage | No |
| /forgot-password | ForgotPasswordPage | No |
| /reset-password | ResetPasswordPage | No |
| /mfa-verify | MfaVerifyPage | No |
| /new-password | NewPasswordPage | No |
| /passwordless | PasswordlessLoginPage | No — only reachable if passwordlessEnabled is set |
| /federated-callback | FederatedCallbackPage | No — Hosted UI redirect target |
| /mfa-setup | MfaSetupPage | Yes |
| /mfa-manage | MfaManagePage | Yes — MFA status, preference, disable |
| /mfa-sms-setup | MfaSmsSetupPage | Yes — associate + verify phone number, enable SMS MFA |
| /change-password | ChangePasswordPage | Yes |
| /profile | ProfilePage | Yes — edit attributes, verify email, delete account |
| /* | Your children routes | Yes (+ requiredGroups if set) |
Props
CognitoAuthRoutesProps
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| config | AuthConfig | required | Cognito user pool config (see @massimo.mazzoleni/ui-cognito-max/core) |
| logo | LogoConfig | — | Logo image to display on auth pages |
| appTitle | string | — | App name shown above the auth card |
| lang | 'it' \| 'en' | 'it' | UI language |
| homePath | string | '/' | Redirect path after successful login |
| requiredGroups | string[] | — | Restricts the children routes to users belonging to at least one of these Cognito groups. Does not apply to account-management pages (/mfa-setup, /mfa-manage, /mfa-sms-setup, /change-password, /profile), which stay reachable by any authenticated user |
| federatedProviders | FederatedProvider[] | — | Social/Hosted UI login buttons shown on LoginPage. Requires config.federated |
| passwordlessEnabled | boolean | false | Shows the "sign in without a password" link on LoginPage, enabling /passwordless (CUSTOM_AUTH flow) |
| deviceTrackingEnabled | boolean | false | Shows "remember this device" (MfaVerifyPage) and the trusted-devices list (MfaManagePage). Requires config.deviceTracking: true |
| smsMfaEnabled | boolean | true | Shows SMS-related MFA options in MfaManagePage (badge, "set up SMS", preference switch). Set to false if your project only supports TOTP (e.g. no SNS role configured on the User Pool) — MfaManagePage then only shows the TOTP reconfigure/disable actions |
| authenticatorEnabled | boolean | true | Shows TOTP-related MFA options in MfaManagePage (badge, "reconfigure authenticator app", first-setup button). Set to false if your project only supports SMS (rare — the typical combination is the opposite, see smsMfaEnabled) |
| disableMFA | boolean | false | Hides the entire MFA section in MfaManagePage, for projects that don't offer MFA self-service at all. Doesn't stop Cognito from still requesting MFA at login if the User Pool has it REQUIRED — purely a self-service UI choice |
| allowDisableMFA | boolean | true | Shows the "Disable MFA" button in MfaManagePage. Set to false for a security policy that allows reconfiguring the authenticator but never turning MFA off entirely. Note: disabling MFA never locks a user out of login — with MfaConfiguration: OPTIONAL it just skips the second factor; with REQUIRED, Cognito ignores the preference and forces MFA_SETUP_REQUIRED again at next login |
| captchaSlot | ReactNode | — | Custom CAPTCHA widget rendered above the submit button in RegisterPage/ForgotPasswordPage |
| getClientMetadata | () => Record<string, string> \| undefined | — | Evaluated right before signUp()/forgotPassword(); the result is sent to Cognito as clientMetadata, readable by a Lambda trigger (e.g. to verify a CAPTCHA token) |
| children | ReactNode | required | Protected app routes |
LogoConfig
interface LogoConfig {
src: string
alt?: string
height?: number // px
}FederatedProvider
interface FederatedProvider {
id: string // passed to getFederatedSignInUrl({ provider: id }) — e.g. 'Google', or your SAML/OIDC provider name
label: string // button text, e.g. "Continue with Google"
}Federated login (Hosted UI / OAuth2 + PKCE)
Requires config.federated (domain, redirectUri, scopes?) and the Hosted UI domain
enabled on the User Pool:
<CognitoAuthRoutes
config={{
userPoolId, clientId, region,
federated: {
domain: 'myapp.auth.eu-west-1.amazoncognito.com',
redirectUri: 'https://myapp.example.com/federated-callback',
},
}}
federatedProviders={[
{ id: 'Google', label: 'Continue with Google' },
]}
>
...
</CognitoAuthRoutes>redirectUri must match the /federated-callback route mounted automatically by
<CognitoAuthRoutes> and must be registered as a callback URL on the Cognito App Client.
Passwordless (CUSTOM_AUTH)
<CognitoAuthRoutes config={cognitoConfig} passwordlessEnabled>
...
</CognitoAuthRoutes>Requires the DefineAuthChallenge/CreateAuthChallenge/VerifyAuthChallengeResponse Lambda
triggers configured on the User Pool: the library only speaks the CUSTOM_AUTH protocol — the
challenge logic itself (e.g. sending an OTP via email) is the trigger's responsibility.
Device tracking ("remember this device")
<CognitoAuthRoutes
config={{ userPoolId, clientId, region, deviceTracking: true }}
deviceTrackingEnabled
>
...
</CognitoAuthRoutes>Requires "Remember user devices" enabled on the User Pool. Adds a checkbox in
MfaVerifyPage and a devices list/forget action in MfaManagePage.
"Remember me"
For a real runtime "stay signed in" toggle (instead of deciding the storage once at config
time), use RememberMeStorageAdapter:
import { RememberMeStorageAdapter } from '@massimo.mazzoleni/ui-cognito-max'
const storage = new RememberMeStorageAdapter()
<CognitoAuthRoutes config={{ userPoolId, clientId, region, storage }}>
...
</CognitoAuthRoutes>LoginPage detects this adapter automatically (instanceof) and shows a "Remember me"
checkbox — without it, the checkbox doesn't appear.
Letting users change their MFA authenticator
<CognitoAuthRoutes> already mounts /mfa-manage (current MFA status, reconfigure TOTP,
disable, remembered devices) and /mfa-setup, but nothing links to them from your app's own
UI — you need an entry point (a settings menu item, a button) that navigates there. Drop-in
components for that:
import { MfaSettingsButton } from '@massimo.mazzoleni/ui-cognito-max'
<MfaSettingsButton lang="it" />or, for data-driven menus (e.g. Cloudscape <TopNavigation> utilities) that need a plain
{ text, onClick } object instead of JSX:
import { useMfaSettingsLink } from '@massimo.mazzoleni/ui-cognito-max'
const { label, onClick } = useMfaSettingsLink('it')Both must be rendered/called from inside the <BrowserRouter> tree where
<CognitoAuthRoutes> mounts its routes (they use useNavigate() internally) — see
CHANGE_MFA_AUTHENTICATOR.md for full examples, the
<TopNavigation> integration, and props reference.
CAPTCHA
The library doesn't bundle a specific CAPTCHA provider — it only exposes a hook point.
<CognitoAuthRoutes
config={cognitoConfig}
captchaSlot={<MyRecaptchaWidget onVerify={setToken} />}
getClientMetadata={() => (token ? { captchaToken: token } : undefined)}
>
...
</CognitoAuthRoutes>getClientMetadata() is called right before signUp()/forgotPassword(); the result is
sent to Cognito as clientMetadata, readable by a Lambda trigger (e.g. PreSignUp) to verify
the token server-side.
Individual pages
You can also use pages individually for custom layouts:
import { LoginPage, ChangePasswordPage, ProfilePage } from '@massimo.mazzoleni/ui-cognito-max'i18n
import { useTranslations } from '@massimo.mazzoleni/ui-cognito-max'
const t = useTranslations('en')SSR / Next.js
Starting from this version, every component (CognitoAuthRoutes, all pages, AuthLayout)
and every hook from @massimo.mazzoleni/ui-cognito-max/react ships with a 'use client'
directive, so they can be imported in a Next.js project (App Router or Pages Router) without
build errors.
Important architectural note: <CognitoAuthRoutes> is built on react-router-dom
(BrowserRouter/Routes/Route), which owns client-side routing independently from
Next.js App Router's file-based routing. To use it in an App Router project, mount the whole
auth block inside a catch-all segment so react-router-dom owns that entire URL subtree:
// app/(auth)/[[...slug]]/page.tsx
'use client'
import { BrowserRouter, Route } from 'react-router-dom'
import { CognitoAuthRoutes } from '@massimo.mazzoleni/ui-cognito-max'
export default function AuthCatchAll() {
return (
<BrowserRouter>
<CognitoAuthRoutes config={cognitoConfig}>
<Route path="/" element={<Dashboard />} />
</CognitoAuthRoutes>
</BrowserRouter>
)
}The rest of the app keeps using Next.js's native routing as usual. With Next.js Pages Router, no special handling is needed: every page is already a client component after hydration.
The default storage (AutoStorageAdapter, used when config.storage is not set) and
RememberMeStorageAdapter are both SSR-safe: during server-side rendering (where window
doesn't exist) they silently become no-op/in-memory instead of throwing.
Troubleshooting (Vite)
Two errors show up specifically with Vite dev servers, both caused by how Vite's dependency
pre-bundling (optimizeDeps) interacts with this library's dependency chain:
SyntaxError: ... does not provide an export named 'Buffer'(inAuthenticationHelper.js) —amazon-cognito-identity-jspins an old CJS-onlybufferpolyfill. Fix: overridebufferto^6.0.3in your package manager config (overrides/resolutions), and make sure Vite pre-bundles it (optimizeDeps.include: ['amazon-cognito-identity-js', 'buffer']). You'll also likely needdefine: { global: 'globalThis' }invite.config.ts— the same polyfill references the Node.jsglobal, which doesn't exist in a browser, and this only surfaces at runtime (e.g. on the first sign-in click), not at build time.useAuthContext must be used inside <AuthProvider>even though it clearly is — happens if your app imports@massimo.mazzoleni/ui-cognito-max/reactdirectly (e.g. for a custom token-bridge component) alongside<CognitoAuthRoutes>, and your dev server ends up creating two separate instances of the package's/reactsubpath (two differentcreateContext()calls). Fix: addresolve.dedupe: ['@massimo.mazzoleni/ui-cognito-max']andoptimizeDeps.exclude: ['@massimo.mazzoleni/ui-cognito-max']tovite.config.ts(combined with theoptimizeDeps.includeabove).
After changing vite.config.ts, delete node_modules/.vite (Vite doesn't always fully
invalidate its cache with --force alone) and restart the dev server.
If you consume this package via a file: link (developing it alongside a consumer app,
rather than installing from npm), see apps/README.md in this repo for a further class of
issue specific to that scenario (webpack/Next.js resolving a second copy of react/
react-dom/react-router-dom through the linked repo's own dev node_modules) — it doesn't
apply to a normal npm install consumer.
Dependencies
@aws-sdk/client-cognito-identity-provider,amazon-cognito-identity-js— Cognito auth logic (bundled)@cloudscape-design/components(peer, optional) — UI components, required only if you import the main.entryqrcode.react(peer, optional) — scannable QR code inMfaSetupPage, required only if you import the main.entryreact-router-dom(peer, optional) — required only if you import the main.entryreact-hook-form(peer, optional) — required only if you import the main.entryreact>= 18,react-dom>= 18 (peer, required — the only two peers needed for/coreor/reactalone)
