@auth-craft/frontend-sdk
v0.1.3
Published
Frontend SDK for Auth Craft — wraps FetchGuard with typed modules for authentication, user management, MFA, OAuth, and tenant operations
Maintainers
Readme
@auth-craft/frontend-sdk
Frontend SDK cho Auth Craft — wrap FetchGuard v2 cung cấp typed modules cho authentication, user management, MFA, OAuth và tenant operations.
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Your App (React / Vue / Svelte / Vanilla) │
│ │
│ import { createAuthCraft } from '@auth-craft/frontend-sdk' │
│ const ac = createAuthCraft(config) │
│ await ac.init() │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ AuthCraftClient │ │
│ │ │ │
│ │ ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │
│ │ │ auth │ │ password │ │ oauth │ │ verification │ │ │
│ │ └────┬────┘ └────┬─────┘ └────┬─────┘ └──────┬───────┘ │ │
│ │ ┌────┴────┐ ┌────┴─────┐ ┌────┴─────┐ ┌──────┴───────┐ │ │
│ │ │ user │ │ session │ │ mfa │ │ tenant │ │ │
│ │ └────┬────┘ └────┬─────┘ └────┬─────┘ └──────┬───────┘ │ │
│ │ │ │ │ │ │ │
│ │ └───────────┴────────────┴───────────────┘ │ │
│ │ │ │ │
│ │ ┌──────────┴──────────┐ │ │
│ │ │ InternalClient │ │ │
│ │ │ get/post/put/patch │ │ │
│ │ │ delete/buildUrl │ │ │
│ │ └──────────┬──────────┘ │ │
│ │ │ │ │
│ │ ┌──────────┴──────────┐ │ │
│ │ │ FetchGuard Client │ │ │
│ │ │ (Web Worker) │ │ │
│ │ └──────────┬──────────┘ │ │
│ └──────────────────────── │ ────────────────────────────────┘ │
│ │ │
│ ┌────────┴────────┐ │
│ │ Worker Thread │ │
│ │ Token Storage │ │
│ │ Auto Refresh │ │
│ └────────┬────────┘ │
└──────────────────────────── │ ───────────────────────────────────┘
│
┌────────┴────────┐
│ Auth Craft API │
│ /auth/... │
│ /users/... │
│ /sessions/... │
│ /mfa/... │
│ /tenant/... │
└─────────────────┘Authentication Flow
authenticate()
│
┌───────────┼───────────┐
▼ ▼ ▼
TokenResponse Challenge MfaRequired
(success) Response Response
│ │ │
│ completeChallenge() │
│ │ │
│ ┌───────┼───────┐ │
│ ▼ ▼ │
│ TokenResponse MfaRequired
│ │ Response
│ │ │
│ │ ┌──────────┘
│ │ │
│ │ ▼ issueMfaChallenge() (optional, SMS/Email)
│ │ │
│ │ ▼ completeMfaChallenge()
│ │ │
│ │ ▼
│ │ TokenResponse
│ │ │
▼ ▼ ▼
┌─────────────────────┐
│ Authenticated! │
│ Token in Worker │
│ onAuthStateChanged │
└─────────────────────┘Install
pnpm add @auth-craft/frontend-sdk
# or
npm install @auth-craft/frontend-sdkPeer Dependencies: fetchguard@^2.2.3, ts-micro-result@^3.3.0
Quick Start
import { createAuthCraft, isTokenResponse, isChallengeResponse } from '@auth-craft/frontend-sdk'
// 1. Create client
const authCraft = createAuthCraft({
baseUrl: 'https://auth.example.com',
strategy: 'cookie-auth', // or 'body-auth'
allowedDomains: ['api.example.com'],
onAuthStateChanged: (state) => {
console.log('Auth changed:', state.authenticated)
},
})
// 2. Initialize (loads Web Worker, restores session)
await authCraft.init()
// 3. Login
const result = await authCraft.auth.authenticate({
identityType: 'email',
identityValue: '[email protected]',
credentialType: 'password',
credentialValue: 's3cret',
})
if (result.ok) {
if (isTokenResponse(result.data)) {
console.log('Logged in!')
} else if (isChallengeResponse(result.data)) {
// Handle email verification challenge
const { challengeId } = result.data.challenge
// ... show OTP input, then:
await authCraft.auth.completeChallenge({ challengeId, code: '123456' })
}
} else {
console.error(result.errors[0].message)
}Configuration
interface AuthCraftConfig {
baseUrl: string // Auth Craft server URL
strategy: 'cookie-auth' | 'body-auth' // Token refresh strategy
allowedDomains: string[] // Domains receiving auth tokens
refreshEarlyMs?: number // Refresh before expiry (default: 5 min)
defaultHeaders?: Record<string, string> // Headers for all requests
onAuthStateChanged?: (state: AuthState) => void // Auth state callback
workerFactory?: () => Worker // Custom Worker factory (required when consuming compiled dist/)
}Strategy
| Strategy | Refresh Token Storage | Use Case |
|---|---|---|
| cookie-auth | httpOnly cookie (server-set) | Web apps with same-domain API |
| body-auth | FetchGuard Web Worker (IndexedDB) | Cross-domain, Lambda/serverless |
workerFactory
Required when the SDK is consumed as compiled JS (e.g. from dist/) since bundlers cannot resolve the worker URL automatically:
const authCraft = createAuthCraft({
baseUrl: 'https://auth.example.com',
strategy: 'body-auth',
allowedDomains: ['api.example.com'],
workerFactory: () => new Worker(new URL('./fetchguard.worker.js', import.meta.url)),
})Modules
auth — Authentication
// Login
ac.auth.authenticate(req: AuthenticateRequest) → Result<AuthenticateResult>
// Register
ac.auth.register(req: RegisterRequest) → Result<RegisterResponse>
// Multi-step auth
ac.auth.completeChallenge(req: CompleteChallengeRequest) → Result<AuthenticateResult>
ac.auth.completeMfaChallenge(req: CompleteMfaChallengeRequest) → Result<TokenResponse>
ac.auth.issueMfaChallenge(req: IssueMfaChallengeRequest) → Result<IssueMfaChallengeResponse>
// Session
ac.auth.logout() → Result<void>
ac.auth.refresh() → { authenticated: boolean }
ac.auth.me() → Result<AuthUserProfile>
// Tenant
ac.auth.selectTenant(tenantId: string) → Result<TokenResponse>password — Password Management
ac.password.change(req: ChangePasswordRequest) → Result<MessageResponse>
ac.password.forgot(req: ForgotPasswordRequest) → Result<ForgotPasswordResponse>
ac.password.reset(req: ResetPasswordRequest) → Result<MessageResponse>verification — Email / SMS Verification
ac.verification.send(req: SendVerificationRequest) → Result<VerificationResponse>
ac.verification.verify(req: VerifyCodeRequest) → Result<VerifyCodeResponse>
ac.verification.resend(req: ResendVerificationRequest)→ Result<VerificationResponse>user — User Profile
ac.user.sendWelcomeEmail(req: WelcomeEmailRequest) → Result<MessageResponse>
ac.user.updateProfile(req: UpdateProfileRequest) → Result<AuthUserProfile>oauth — Social Login
ac.oauth.callback(req: OAuthCallbackRequest) → Result<TokenResponse>
ac.oauth.link(req: OAuthLinkRequest) → Result<MessageResponse>
ac.oauth.unlink(req: OAuthUnlinkRequest) → Result<MessageResponse>
ac.oauth.listProviders() → Result<ListOAuthProvidersResponse>session — Session Management
ac.session.list(includeRevoked?: boolean) → Result<ListSessionsResponse>
ac.session.delete(req: DeleteSessionRequest) → Result<MessageResponse>
ac.session.revoke(req: RevokeSessionRequest) → Result<MessageResponse>mfa — Multi-Factor Authentication
ac.mfa.setup(req: MfaSetupRequest) → Result<MfaSetupResponse>
ac.mfa.verify(req: MfaVerifyRequest) → Result<MfaVerifyResponse>
ac.mfa.list(options?: { method?: string; verifiedOnly?: boolean })
→ Result<ListMfaAuthenticatorsResponse>
ac.mfa.delete(req: DeleteMfaAuthenticatorRequest) → Result<MessageResponse>
ac.mfa.updateSettings(req: UpdateMfaSettingsRequest) → Result<MfaSettings>tenant — Tenant / Organization
// Invites
ac.tenant.createInvite(req: CreateTenantInviteRequest) → Result<CreateTenantInviteResponse>
ac.tenant.listInvites(status?: string) → Result<{ invites: TenantInvite[] }>
ac.tenant.acceptInvite(req: AcceptInviteRequest) → Result<AcceptInviteResponse>
ac.tenant.declineInvite(req: DeclineInviteRequest) → Result<MessageResponse>
ac.tenant.revokeInvite(req: RevokeInviteRequest) → Result<MessageResponse>
ac.tenant.checkPendingInvites(req: CheckPendingInvitesRequest) → Result<{ invites: PendingInvite[] }>
// Members
ac.tenant.listMembers() → Result<ListTenantMembersResponse>
ac.tenant.updateMember(req: UpdateTenantMemberRequest) → Result<MessageResponse>
ac.tenant.removeMember(req: RemoveTenantMemberRequest) → Result<MessageResponse>
// Profile
ac.tenant.getProfile() → Result<TenantProfile>
ac.tenant.updateProfile(req: UpdateTenantProfileRequest) → Result<MessageResponse>Response Types
AuthenticateResult
Login/challenge methods return a union — use type guards to discriminate:
import {
isTokenResponse,
isChallengeResponse,
isMfaRequiredResponse,
} from '@auth-craft/frontend-sdk'
const result = await ac.auth.authenticate(req)
if (!result.ok) {
// result.errors: readonly ErrorDetail[]
return
}
if (isTokenResponse(result.data)) {
// { accessToken, expiresAt }
}
if (isChallengeResponse(result.data)) {
// { requiresChallenge: true, challenge: { challengeId, phase, expiresAt, data? } }
}
if (isMfaRequiredResponse(result.data)) {
// { requiresMfa: true, mfa: { phase, challengeId, methods, method?, expiresAt, userId } }
}Result<T> Pattern
All methods return Result<T> from ts-micro-result:
type Result<T> =
| { ok: true; data: T; meta?: unknown }
| { ok: false; errors: readonly ErrorDetail[]; meta?: unknown }
interface ErrorDetail {
code: string
message: string
field?: string
params?: Record<string, unknown>
}HTTP Client
AuthCraftClient also exposes authenticated HTTP methods for custom API calls:
// All return Result<T> — auto-handles token injection, refresh, error transform
await ac.get<User>(ac.buildUrl('/users/123'))
await ac.post<Order>(ac.buildUrl('/orders'), { items: [...] })
await ac.put<void>(url, body)
await ac.patch<void>(url, body)
await ac.delete<void>(url, body)ac.buildUrl(path) constructs full URLs: {baseUrl}{path}. Pass absolute URLs for other APIs.
Lifecycle
// Create + init
const ac = createAuthCraft(config)
await ac.init() // creates Web Worker, restores session, triggers onAuthStateChanged
// Use...
// Cleanup (SPA unmount)
ac.destroy() // terminates Web Worker, clears stateError Codes
SDK-level errors (from SdkErrors):
| Code | Description |
|---|---|
| NOT_INITIALIZED | init() not called |
| LOGIN_FAILED | Authentication failed |
| NETWORK_ERROR | Connection failed |
| SERVER_ERROR | 5xx from server |
| GET_FAILED / POST_FAILED / ... | HTTP method error |
| PARSE_ERROR | Response JSON parse failed |
Backend errors are passed through as-is in result.errors.
Project Structure
src/
├── index.ts # Public API exports
├── client.ts # AuthCraftClient (FetchGuard wrapper)
├── config.ts # AuthCraftConfig interface
├── errors.ts # SdkErrors factories
├── utils.ts # Response transform, error mapping
├── types/
│ ├── index.ts # Type barrel
│ ├── auth.ts # Auth request/response types
│ ├── user.ts # User profile types
│ ├── password.ts # Password types
│ ├── verification.ts # Verification types
│ ├── session.ts # Session types
│ ├── mfa.ts # MFA types
│ ├── oauth.ts # OAuth types
│ └── tenant.ts # Tenant types
└── modules/
├── auth.ts # AuthModule
├── user.ts # UserModule
├── password.ts # PasswordModule
├── verification.ts # VerificationModule
├── session.ts # SessionModule
├── mfa.ts # MfaModule
├── oauth.ts # OAuthModule
└── tenant.ts # TenantModuleIntegration Example (merchant-web)
Khi tích hợp vào FE app, dùng ApiClient pattern — 1 singleton quản lý lifecycle, expose 2 namespace:
// ApiClient.ts — Composition Root
import { createAuthCraft, type AuthCraftClient } from '@auth-craft/frontend-sdk'
class ApiClientImpl {
private _sdk: AuthCraftClient | null = null
/** Auth operations (login, MFA, password, verification, OAuth, etc.) */
get auth(): AuthCraftClient {
if (!this._sdk) throw new Error('ApiClient not initialized')
return this._sdk
}
/** Authenticated API for feature services (orders, stores, wallet, etc.) */
readonly api = {
get: <T>(url: string, opts?) => this.auth.get<T>(url, opts),
post: <T>(url: string, body?, opts?) => this.auth.post<T>(url, body, opts),
put: <T>(url: string, body?, opts?) => this.auth.put<T>(url, body, opts),
patch: <T>(url: string, body?, opts?) => this.auth.patch<T>(url, body, opts),
delete: <T>(url: string, body?, opts?) => this.auth.delete<T>(url, body, opts),
}
async init() { /* createAuthCraft + init + wire auth state bridge */ }
destroy() { /* cleanup */ }
}
export const apiClient = new ApiClientImpl()Usage:
// Auth service (auth operations)
apiClient.auth.auth.authenticate({ ... })
apiClient.auth.password.forgot({ email })
apiClient.auth.oauth.callback({ provider: 'google', code })
// Feature services (business API)
apiClient.api.get<Order[]>('/orders')
apiClient.api.post<Store>('/stores', data)
// Lifecycle (AuthProvider)
await apiClient.init()
apiClient.destroy()┌───────────────────────────────────────────────────────────┐
│ FE App │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ apiClient (singleton) │ │
│ │ │ │
│ │ .auth → AuthCraftClient (SDK modules) │ │
│ │ .api → authenticated HTTP (get/post/put/...) │ │
│ │ .init() / .destroy() │ │
│ └────────┬──────────────┬─────────────────────────────┘ │
│ │ │ │
│ .api (HTTP) .auth (SDK) │
│ │ │ │
│ ┌────────▼───────┐ ┌──▼──────────────────────┐ │
│ │ feature svcs │ │ authService │ │
│ │ orders, stores │ │ login, register, MFA │ │
│ │ items, wallet │ │ password, verification │ │
│ │ ... │ │ OAuth, tenant │ │
│ └────────────────┘ └──────────────────────────┘ │
└───────────────────────────────────────────────────────────┘License
MIT
