@stackra/auth
v1.1.0
Published
Headless authentication runtime — AuthService + SessionService + SecurityService + LastUsedMethodService, 22 React hooks (login / logout / register / MFA / social / passkey / phone / magic-link / …), 7 Zod schemas, and the AuthAIContextListener that feeds
Maintainers
Readme
@stackra/auth
Headless authentication runtime — AuthService + SessionService +
SecurityService + LastUsedMethodService, 22 React hooks (login / logout /
register / MFA / social / passkey / phone / magic-link / …), 7 Zod schema
factories, and the AuthAiContextListener that feeds auth state into
@stackra/ai. Phase 2 of the frontend auth split per
ADR-0037.
Compose with @stackra/auth-ui for the ready-to-use
form kit, or plug into a bespoke UI directly. Permission-side hooks + gates live
in @stackra/authorization — install both
packages when the app needs auth + permission checks.
Install
pnpm add @stackra/auth @stackra/authorization @stackra/container \
@stackra/contracts @stackra/http @stackra/storage \
@stackra/support reflect-metadata zodQuick start
import { AuthModule } from "@stackra/auth";
import { AuthorizationModule } from "@stackra/authorization";
import { Module } from "@stackra/container";
import { HttpModule } from "@stackra/http";
@Module({
imports: [
HttpModule.forRoot({
default: "api",
connections: { api: { baseURL: "/api" } },
}),
AuthorizationModule.forRoot(),
AuthModule.forRoot({
api: { baseURL: "/api/auth" },
minPasswordLength: 12,
otpLength: 6,
}),
],
})
export class AppModule {}import { useLogin } from "@stackra/auth/react";
function SignInForm() {
const { mutate, loading, error } = useLogin();
return (
<form onSubmit={(e) => (e.preventDefault(), mutate({ email, password }))}>
...
</form>
);
}Public API
. — module, services, schemas, tokens, registries, listeners
AuthModule.forRoot(options)/.forRootAsync(options)— DI module + named"auth"HTTP connection.AuthService— login / logout / register / challenge / verify / password reset + update / link + unlink provider / check / getIdentity / getSession / getPermissions / onError / passkey (4 methods). Contract:IAuthService(from@stackra/contracts); DI token:AUTH_SERVICE(also from contracts).SessionService— active devices, session refresh + destroy, cross-tab sync viaIEventEmitter. DI token:SESSION_SERVICE(from contracts).SecurityService— password strength (IPasswordStrengthReport), breach check, device management. DI token:SECURITY_SERVICE(from contracts).LastUsedMethodService— remember the last successful login method.StorageManagerSessionStorage— the shippedISessionStorageimplementation that routes through@stackra/storage.AuthVariantRegistry— variant → layout mapping (consumed by@stackra/auth-ui).AuthAiContextListener— feeds identity into@stackra/aivia@OnEvent; no-op when@stackra/aiisn't installed.- Zod schema factories:
createLoginSchema,createRegisterSchema,createForgotPasswordSchema,createResetPasswordSchema,createUpdatePasswordSchema,createOtpSchema,createPhoneSchema,createBackupCodeSchema. - Package-owned tokens:
BIOMETRIC_UNLOCKER,BROWSER_OPENER,SESSION_STORAGE. Contracts forIBiometricUnlocker,IBrowserOpener, andISessionStorage. Web bindsStorageManagerSessionStorageunderSESSION_STORAGEby default; the./nativesubpath swaps inSecureStoreSessionStorage+ExpoWebBrowserOpener+ optionalExpoLocalAuthenticationUnlocker. - Constants:
AUTH_DEFAULT_STORAGE_INSTANCE,AUTH_SLOTS,AUTH_STORAGE_KEYS. - Utility:
mergeConfig(options?),resolveRoutePath(...),IResolvedAuthConfig.
./react — hooks
Query hooks: useSession, useActiveDevices, useGetIdentity,
useAuthConfig, useAuthVariantRegistry, useLastMethod.
Mutation hooks: useLogin, useLogout, useRegister, useChallenge,
useVerify, useForgotPassword, useResetPassword, useUpdatePassword,
useLinkProvider, useUnlinkProvider, usePasskey, useMagicLink,
usePhoneLogin, useLockScreen.
Utility hooks: useCapsLock, useOtpCooldown.
Permission-side hooks (useCan, useIsAuthenticated, useIdentity,
usePermissions, useSecurity) ship from
@stackra/authorization/react — Phase 1.
./native — React Native subpath
The @stackra/auth/native subpath ships the same DI contract as core, wired to
platform-native adapters:
NativeAuthModule.forRoot(options)— composesAuthModule.forRoot()and swaps three tokens:SESSION_STORAGE→SecureStoreSessionStorage(iOS Keychain / Android Keystore viaexpo-secure-store— neverAsyncStoragefor tokens).BROWSER_OPENER→ExpoWebBrowserOpener(OAuth PKCE viaWebBrowser.openAuthSessionAsync).BIOMETRIC_UNLOCKER→ExpoLocalAuthenticationUnlocker— gated byoptions.biometric === true.
useNativeAuth()— composed hook exposingunlockWithBiometric(promptMessage?),getBiometricAvailability(),signInWithGoogle(options?),signInWithApple(options?), andsignInWithProvider(provider, options?).
import { Module } from "@stackra/container";
import { HttpModule } from "@stackra/http";
import { NativeAuthModule, useNativeAuth } from "@stackra/auth/native";
import { NativeStorageModule } from "@stackra/storage/native";
@Module({
imports: [
HttpModule.forRoot({
default: "api",
connections: { api: { baseURL: "https://api.example.com" } },
}),
NativeStorageModule.forRoot(),
NativeAuthModule.forRoot({
api: { baseURL: "https://api.example.com/v1/auth" },
biometric: true,
oauth: {
redirectUrl: "stackra://auth/callback",
authorizationUrls: {
google:
"https://accounts.google.com/o/oauth2/v2/auth?client_id=x&scope=email profile",
apple:
"https://appleid.apple.com/auth/authorize?client_id=y&scope=name email",
},
},
}),
],
})
export class AppModule {}
function SignInScreen() {
const { signInWithGoogle, unlockWithBiometric, biometricPending } =
useNativeAuth();
const handleGoogle = async () => {
const { type, redirectUrl } = await signInWithGoogle();
if (type === "success" && redirectUrl) {
// Extract `code` from `redirectUrl` and feed into useLogin(...) to
// complete the PKCE exchange.
}
};
return (
<View>
<Button title="Sign in with Google" onPress={handleGoogle} />
<Button
title="Unlock with Face ID"
disabled={biometricPending}
onPress={() => unlockWithBiometric("Unlock Stackra")}
/>
</View>
);
}Native peer requirements
All optional — install the peers you actually use:
pnpm add react-native expo-secure-store expo-web-browser expo-local-authenticationUniversal-link / deep-link setup
expo-web-browser's openAuthSessionAsync waits for a redirect whose scheme
matches the redirectUrl passed on each call. Register the same URL:
- iOS — add
CFBundleURLTypesinInfo.plist+ theapplinks:entitlement for Universal Links. - Android — add an
intent-filterinAndroidManifest.xmlwithandroid:autoVerify="true".
iOS Info.plist requirement for biometric unlock
Face ID prompts crash the app on iOS 14+ without NSFaceIDUsageDescription in
Info.plist. Add a purpose string before shipping.
./testing — fakes + helpers
MockAuthService— deterministic in-memoryIAuthServicefake with a fluent.setAuthenticated(...).setIdentity(...).setPermissions(...)API.MockSessionService— deterministic session fake. Exposes anIMockSessionServiceApifor advanced test choreography.renderWithAuth(ui, options)— mounts an in-memory container withAUTH_SERVICE,SESSION_SERVICE,SECURITY_SERVICE, andACCESS_CONTROL_SERVICEbound. ReturnsIRenderWithAuthResultwith the mounted RTL wrapper. Perfect for React Testing Library.
import { renderWithAuth, MockAuthService } from "@stackra/auth/testing";
test("shows the logged-in user's name", async () => {
const mockAuth = new MockAuthService()
.setAuthenticated(true)
.setIdentity({ id: "u1", name: "Ada", email: "[email protected]" });
const { getByText } = renderWithAuth(<UserBadge />, {
authService: mockAuth,
});
expect(getByText("Ada")).toBeInTheDocument();
});Related
- ADR-0037 — the 3-package client split this file instantiates.
- Backend contract:
packages/backend/access/auth/. - Sibling packages:
@stackra/authorization(Phase 1, permission-side gates + guards),@stackra/auth-ui(Phase 3, HeroUI form kit),@stackra/rbac(Phase 3, RBAC admin surface),@stackra/invitations(Phase 4a),@stackra/delegation(Phase 4b),@stackra/grants(Phase 4c),@stackra/access-requests(Phase 4d).
License
MIT © Figentra L.L.C.
