@modern-innovations/cognito-auth
v1.1.1
Published
Reusable AWS Cognito authentication library with React Query hooks and AuthProvider
Downloads
19
Maintainers
Readme
@modern-innovations/cognito-auth
Reusable AWS Cognito authentication library for React apps. Direct REST calls (no SDK dependency), React Query hooks, and a full AuthProvider with token management.
Features
- Email + password authentication (signup, login, forgot password)
- Social login (Google, Facebook, Apple, Amazon) via Cognito Hosted UI — optional
- Email verification (OTP)
- MFA support (TOTP authenticator app + email)
- Auto token refresh with 60-second buffer
- localStorage session persistence with configurable prefix
- React Query mutations for loading/error state
- Extensible via callbacks and generics
- Zero AWS SDK dependencies — direct REST calls only
- TypeScript-first with full type exports
Installation
npm install @modern-innovations/cognito-authPeer dependencies:
npm install react @tanstack/react-queryAPI Reference
AuthProvider
Wrap your app with AuthProvider to enable all auth hooks.
import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
import { AuthProvider } from "@modern-innovations/cognito-auth";
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<AuthProvider
config={{
region: "us-east-1",
userPoolId: "us-east-1_XXXXXX",
clientId: "your-app-client-id",
}}
storagePrefix="myapp.auth"
onLoginSuccess={async (tokens, user) => {
const profile = await fetchProfile(tokens.accessToken);
return { ...user, ...profile };
}}
onLogout={() => {
queryClient.clear();
}}
>
<YourApp />
</AuthProvider>
</QueryClientProvider>
);
}Props
| Prop | Type | Required | Default | Description |
| ----------------- | ------------------------------------------------- | -------- | ---------------- | ------------------------------------------------------------------------------ |
| config | CognitoAuthConfig | Yes | — | Cognito pool connection config |
| oauthConfig | OAuthConfig | No | — | OAuth / Hosted UI config for social logins (see Social Login) |
| storagePrefix | string | No | "cognito.auth" | Prefix for localStorage keys ({prefix}.user, {prefix}.tokens) |
| onLoginSuccess | (tokens, user) => Promise<AuthUser> \| AuthUser | No | — | Callback after login — extend user with custom data from your backend |
| onSignupSuccess | (userSub, email) => Promise<void> \| void | No | — | Callback after successful signup |
| onLogout | () => void | No | — | Callback on logout — useful for clearing caches |
CognitoAuthConfig
| Property | Type | Description |
| ------------ | -------- | ------------------------------------------------ |
| region | string | AWS region (e.g. "us-east-1") |
| userPoolId | string | Cognito User Pool ID (e.g. "us-east-1_XXXXXX") |
| clientId | string | Cognito App Client ID |
OAuthConfig
| Property | Type | Required | Default | Description |
| ------------------- | ------------------- | -------- | -------------------------------- | -------------------------------------------------------------------------- |
| domain | string | Yes | — | Cognito Hosted UI domain (e.g. "myapp.auth.us-east-1.amazoncognito.com") |
| redirectUri | string | Yes | — | Redirect URI after social login (must match Cognito app client) |
| logoutRedirectUri | string | No | Same as redirectUri | Redirect URI after sign-out |
| scopes | string[] | No | ["openid", "email", "profile"] | OAuth scopes |
| responseType | "code" \| "token" | No | "code" | OAuth response type |
useAuth()
Access the auth state and actions. Must be used within AuthProvider.
const {
user,
tokens,
isAuthenticated,
isHydrated,
loginWithTokens,
setUser,
logout,
} = useAuth();Returns: AuthState
| Property | Type | Description |
| ----------------- | ------------------------------ | -------------------------------------------------------------------------- |
| user | AuthUser \| null | Current authenticated user |
| tokens | AuthTokens \| null | Current JWT tokens (id, access, refresh) |
| isAuthenticated | boolean | true if user and tokens are present |
| isHydrated | boolean | true after localStorage rehydration is complete (use for loading states) |
| loginWithTokens | (tokens: AuthTokens) => void | Store tokens, decode user from ID token, trigger onLoginSuccess |
| setUser | (user: AuthUser) => void | Update user profile data in state and storage |
| logout | () => void | Clear tokens/user, call global sign-out, trigger onLogout |
useAuthService()
Access the raw AuthService instance for advanced or custom flows.
const authService = useAuthService();
// Example: manually refresh session
const refreshed = await authService.refreshSession(refreshToken);useOAuth()
Access the OAuthService for social login. Only available when oauthConfig is passed to AuthProvider.
const oauth = useOAuth();
// Redirect to Google login
oauth.federatedSignIn("Google");See Social Login section for full usage.
Hooks
All hooks return a React Query UseMutationResult with mutate, mutateAsync, isPending, isError, error, data, etc.
useLogin()
Sign in with email and password.
const loginMutation = useLogin();
loginMutation.mutate(
{ email: "[email protected]", password: "P@ssw0rd" },
{
onSuccess: (tokens) => {
loginWithTokens(tokens);
},
},
);Input: LoginInput
| Field | Type | Required | Description |
| ---------- | -------- | -------- | --------------- |
| email | string | Yes | User's email |
| password | string | Yes | User's password |
Returns: AuthTokens
useSignup()
Register a new user.
const signupMutation = useSignup();
signupMutation.mutate({
email: "[email protected]",
password: "P@ssw0rd",
firstName: "John",
lastName: "Doe",
middleName: "Michael", // optional
customAttributes: { "custom:company": "Acme" },
});Input: SignupInput
| Field | Type | Required | Description |
| ------------------ | ------------------------ | -------- | ---------------------------------------------------------- |
| email | string | Yes | User's email |
| password | string | Yes | Password |
| firstName | string | Yes | Given name |
| lastName | string | Yes | Family name |
| middleName | string | No | Middle name (stored as middle_name attribute in Cognito) |
| customAttributes | Record<string, string> | No | Additional Cognito user attributes (e.g. custom:company) |
Returns: { userSub: string }
useConfirmSignup()
Confirm email verification with the OTP code.
const confirmMutation = useConfirmSignup();
confirmMutation.mutate({ email: "[email protected]", code: "123456" });Input: ConfirmSignupInput
| Field | Type | Required | Description |
| ------- | -------- | -------- | --------------------------- |
| email | string | Yes | User's email |
| code | string | Yes | 6-digit OTP code from email |
Returns: void
useResendCode()
Resend the signup confirmation code.
const resendMutation = useResendCode();
resendMutation.mutate({ email: "[email protected]" });Input: ResendCodeInput
| Field | Type | Required | Description |
| ------- | -------- | -------- | ------------ |
| email | string | Yes | User's email |
Returns: void
useForgotPassword()
Initiate password reset — sends a code to the user's email.
const forgotMutation = useForgotPassword();
forgotMutation.mutate({ email: "[email protected]" });Input: ForgotPasswordInput
| Field | Type | Required | Description |
| ------- | -------- | -------- | ------------ |
| email | string | Yes | User's email |
Returns: void
useConfirmForgotPassword()
Complete password reset with code and new password.
const resetMutation = useConfirmForgotPassword();
resetMutation.mutate({
email: "[email protected]",
code: "123456",
newPassword: "NewP@ssw0rd",
});Input: ConfirmForgotPasswordInput
| Field | Type | Required | Description |
| ------------- | -------- | -------- | --------------------- |
| email | string | Yes | User's email |
| code | string | Yes | Reset code from email |
| newPassword | string | Yes | New password |
Returns: void
useAssociateTotpToken()
Get a TOTP secret to generate a QR code for authenticator apps.
const associateMutation = useAssociateTotpToken();
associateMutation.mutate(tokens.accessToken, {
onSuccess: ({ secretCode }) => {
const uri = buildTotpUri(secretCode, user.email, "MyApp");
const qrUrl = getQrCodeUrl(uri);
// Display QR code to user
},
});Input: string (accessToken)
Returns: AssociateTotpResult
| Field | Type | Description |
| ------------ | --------------------- | ---------------------------------- |
| secretCode | string | TOTP secret for QR code generation |
| session | string \| undefined | Optional session token |
useVerifyTotpToken()
Verify the TOTP code entered by the user from their authenticator app.
const verifyMutation = useVerifyTotpToken();
verifyMutation.mutate({
accessToken: tokens.accessToken,
code: "123456",
friendlyDeviceName: "My Phone",
});Input: VerifyTotpInput
| Field | Type | Required | Description |
| -------------------- | -------- | -------- | --------------------------------------------- |
| accessToken | string | Yes | Current access token |
| code | string | Yes | 6-digit TOTP code from authenticator app |
| friendlyDeviceName | string | No | Device label (default: "Authenticator App") |
Returns: void
useSetMfaPreference()
Set the user's MFA method preference.
const mfaMutation = useSetMfaPreference();
// Enable TOTP as preferred MFA
mfaMutation.mutate({
accessToken: tokens.accessToken,
totpEnabled: true,
totpPreferred: true,
});
// Enable email MFA instead
mfaMutation.mutate({
accessToken: tokens.accessToken,
emailEnabled: true,
emailPreferred: true,
});Input: SetMfaPreferenceInput
| Field | Type | Required | Description |
| ---------------- | --------- | -------- | ----------------------------- |
| accessToken | string | Yes | Current access token |
| totpEnabled | boolean | No | Enable/disable TOTP MFA |
| totpPreferred | boolean | No | Set TOTP as preferred method |
| emailEnabled | boolean | No | Enable/disable email MFA |
| emailPreferred | boolean | No | Set email as preferred method |
Returns: void
Social Login
Social login is optional. If you don't pass oauthConfig to AuthProvider, all social login features are simply not available — no extra bundle cost.
Setup
<AuthProvider
config={{
region: "us-east-1",
userPoolId: "us-east-1_XXXXXX",
clientId: "your-app-client-id",
}}
oauthConfig={{
domain: "myapp.auth.us-east-1.amazoncognito.com",
redirectUri: "http://localhost:3000/auth/callback",
logoutRedirectUri: "http://localhost:3000",
scopes: ["openid", "email", "profile"],
}}
>Supported Providers
| Provider | Value |
| -------- | ------------ |
| Google | "Google" |
| Apple | "Apple" |
| LinkedIn | "LinkedIn" |
Redirecting to Social Login
import { useOAuth } from "@modern-innovations/cognito-auth";
function LoginPage() {
const oauth = useOAuth();
return (
<div>
<button onClick={() => oauth.federatedSignIn("Google")}>
Continue with Google
</button>
<button onClick={() => oauth.federatedSignIn("LinkedIn")}>
Continue with LinkedIn
</button>
<button onClick={() => oauth.federatedSignIn("Apple")}>
Continue with Apple
</button>
</div>
);
}Handling the Callback
After the user signs in with their social provider, they're redirected back to your redirectUri with a ?code= parameter. Exchange it for tokens:
import { useEffect } from "react";
import {
useAuth,
useOAuth,
OAuthService,
} from "@modern-innovations/cognito-auth";
function AuthCallbackPage() {
const { loginWithTokens } = useAuth();
const oauth = useOAuth();
useEffect(() => {
const code = OAuthService.parseCodeFromUrl();
if (code) {
oauth.exchangeCodeForTokens(code).then((tokens) => {
loginWithTokens(tokens);
// Navigate to dashboard
window.history.replaceState({}, "", "/dashboard");
});
}
}, []);
return <p>Signing you in...</p>;
}OAuthService Methods
| Method | Description |
| --------------------------------------- | ----------------------------------------------------- |
| federatedSignIn(provider, state?) | Redirect user to the social provider login page |
| getAuthorizationUrl(provider, state?) | Get the authorization URL without redirecting |
| exchangeCodeForTokens(code) | Exchange auth code for tokens (call on callback page) |
| getLogoutUrl() | Get the Hosted UI logout URL |
| OAuthService.parseCodeFromUrl(url?) | Static — extract ?code= from URL |
| OAuthService.parseStateFromUrl(url?) | Static — extract ?state= from URL |
Cognito Setup for Social Login
- In Cognito Console → Sign-in experience → Federated identity provider sign-in, add your providers (Google, Facebook, etc.)
- In App integration → App client → Hosted UI:
- Set Allowed callback URLs (must match
redirectUri) - Set Allowed sign-out URLs (must match
logoutRedirectUri) - Enable the identity providers you want
- Set OAuth grant type to "Authorization code grant"
- Set OpenID Connect scopes to
openid,email,profile
- Set Allowed callback URLs (must match
- Configure a Cognito domain (or custom domain) under App integration
TOTP Utilities
Helper functions for building TOTP authenticator flows.
buildTotpUri(secret, email, issuer?)
Generate an otpauth:// URI for authenticator apps.
import { buildTotpUri } from "@modern-innovations/cognito-auth";
const uri = buildTotpUri("JBSWY3DPEHPK3PXP", "[email protected]", "MyApp");
// → "otpauth://totp/MyApp:user%40example.com?secret=JBSWY3DPEHPK3PXP&issuer=MyApp&algorithm=SHA1&digits=6&period=30"| Param | Type | Default | Description |
| -------- | -------- | ------- | ---------------------------------------- |
| secret | string | — | TOTP secret from useAssociateTotpToken |
| email | string | — | User's email |
| issuer | string | "App" | App name shown in authenticator |
formatSecret(secret)
Format a secret into groups of 4 for manual entry display.
import { formatSecret } from "@modern-innovations/cognito-auth";
formatSecret("JBSWY3DPEHPK3PXP");
// → "JBSW Y3DP EHPK 3PXP"getQrCodeUrl(data, size?)
Generate a QR code image URL using a public API.
import { getQrCodeUrl } from "@modern-innovations/cognito-auth";
const qrUrl = getQrCodeUrl(totpUri, 250);
// → "https://api.qrserver.com/v1/create-qr-code/?size=250x250&data=..."| Param | Type | Default | Description |
| ------ | -------- | ------- | --------------------------------------- |
| data | string | — | Data to encode (typically the TOTP URI) |
| size | number | 200 | QR image size in pixels |
For production, consider using a local QR library (e.g.
qrcode) instead of the public API.
Error Handling
All hooks throw CognitoAuthError on Cognito-specific failures.
import { CognitoAuthError } from "@modern-innovations/cognito-auth";
loginMutation.mutate(credentials, {
onError: (error) => {
if (error instanceof CognitoAuthError) {
console.log(error.code); // e.g. "NotAuthorizedException"
console.log(error.message); // e.g. "Incorrect username or password."
}
},
});Common Cognito error codes:
| Code | Meaning |
| -------------------------- | ---------------------------- |
| NotAuthorizedException | Incorrect email or password |
| UserNotFoundException | User does not exist |
| UsernameExistsException | Email already registered |
| CodeMismatchException | Invalid verification code |
| ExpiredCodeException | Verification code expired |
| InvalidPasswordException | Password doesn't meet policy |
| LimitExceededException | Too many attempts, try later |
Types
All types are exported for use in your application:
import type {
CognitoAuthConfig,
AuthProviderOptions,
AuthTokens,
AuthUser,
AuthState,
SignupInput,
ConfirmSignupInput,
LoginInput,
ForgotPasswordInput,
ConfirmForgotPasswordInput,
ResendCodeInput,
AssociateTotpResult,
VerifyTotpInput,
SetMfaPreferenceInput,
MfaMethod,
OAuthConfig,
SocialProvider,
ExchangeCodeInput,
} from "@modern-innovations/cognito-auth";AuthTokens
| Field | Type | Description |
| -------------- | -------- | -------------------------------------- |
| idToken | string | JWT ID token (contains user claims) |
| accessToken | string | JWT access token (for API calls) |
| refreshToken | string | Refresh token (for getting new tokens) |
| expiresIn | number | Token lifetime in seconds |
AuthUser
| Field | Type | Description |
| --------------- | --------------------- | ------------------------------ |
| email | string | User's email |
| firstName | string | Given name |
| lastName | string | Family name |
| middleName | string \| undefined | Middle name (optional) |
| sub | string | Cognito user ID |
| [key: string] | unknown | Extensible — add custom fields |
Extending the User Type
import type { AuthUser } from "@modern-innovations/cognito-auth";
interface MyAppUser extends AuthUser {
organisation: string;
role: "admin" | "member";
}Route Protection Example
import { useAuth } from "@modern-innovations/cognito-auth";
function ProtectedLayout() {
const { isAuthenticated, isHydrated } = useAuth();
if (!isHydrated) return <Loading />;
if (!isAuthenticated) return <Navigate to="/login" />;
return <Outlet />;
}Full MFA Setup Flow
import {
useAuth,
useAssociateTotpToken,
useVerifyTotpToken,
useSetMfaPreference,
buildTotpUri,
getQrCodeUrl,
formatSecret,
} from "@modern-innovations/cognito-auth";
function MfaSetupPage() {
const { tokens } = useAuth();
const associate = useAssociateTotpToken();
const verify = useVerifyTotpToken();
const setPreference = useSetMfaPreference();
// Step 1: Get the secret
const handleSetup = () => {
associate.mutate(tokens!.accessToken, {
onSuccess: ({ secretCode }) => {
const uri = buildTotpUri(secretCode, user.email, "MyApp");
setQrUrl(getQrCodeUrl(uri));
setManualCode(formatSecret(secretCode));
},
});
};
// Step 2: Verify the code from the authenticator app
const handleVerify = (code: string) => {
verify.mutate(
{ accessToken: tokens!.accessToken, code },
{
onSuccess: () => {
// Step 3: Set TOTP as preferred MFA
setPreference.mutate({
accessToken: tokens!.accessToken,
totpEnabled: true,
totpPreferred: true,
});
},
},
);
};
}Cognito Setup Requirements
Your AWS Cognito User Pool must be configured with:
- Sign-in attribute: Email
- App Client auth flows:
ALLOW_USER_PASSWORD_AUTHandALLOW_REFRESH_TOKEN_AUTH - MFA: Set to "Optional" with TOTP and/or Email enabled
- Email verification: Enabled
- App Client: No client secret (required for browser-based auth)
- User attributes:
given_name,family_namerequired;middle_nameoptional
License
UNLICENSED
