@amaster.ai/auth-client
v1.1.64
Published
Authentication SDK for Amaster platform
Readme
@amaster.ai/auth-client
Authentication SDK for Amaster Platform - Complete client-side authentication solution with OAuth, session management, and permission checks.
Features
- 🔐 Multiple Authentication Methods: Username/Email/Phone + Password, Verification Code, OAuth
- 🔄 Automatic Token Refresh: JWT token auto-refresh before expiration
- 📦 Token Storage: localStorage or sessionStorage with SSR support
- 🎯 Permission System: Role-based and permission-based access control
- 👤 Anonymous Access: Automatic support for anonymous users with configurable permissions
- 🔗 OAuth Integration: Google, GitHub, WeChat OAuth, WeChat Mini Program, and custom OAuth providers
- 📡 Event System: Listen to login, logout, and token events
- 💾 Session Management: View and revoke active sessions
- 🔒 Type-Safe: Full TypeScript support
Installation
pnpm add @amaster.ai/auth-client
# or
npm install @amaster.ai/auth-clientQuick Start
import { createAuthClient } from "@amaster.ai/auth-client";
// Initialize the client (with optional callbacks)
const authClient = createAuthClient({
onTokenExpired: () => {
window.location.href = "/login";
},
onUnauthorized: () => {
window.location.href = "/login";
},
});
// Register a new user
await authClient.register({
email: "[email protected]",
password: "Password@123",
displayName: "John Doe",
});
// Login
const result = await authClient.login({
loginType: "email",
email: "[email protected]",
password: "Password@123",
});
if (result.data) {
console.log("Logged in:", result.data.user);
}
// Get current user
const user = await authClient.getMe();
// Check permissions
if (authClient.hasPermission("user", "read")) {
// Show user list
}Configuration
Zero Configuration (Recommended)
The SDK works out of the box with zero configuration:
const authClient = createAuthClient();
// Storage auto-detects environment:
// - Browser → localStorage
// - WeChat Mini Program → wx.setStorageSync
// - SSR/Node.js → no-op (no persistence)Optional Configuration
interface AuthClientOptions {
baseURL?: string; // API base URL, defaults to window.location.origin
headers?: Record<string, string>; // Custom headers for all requests
onTokenExpired?: () => void; // Token expiration callback
onUnauthorized?: () => void; // Unauthorized (401) callback
autoHandleOAuthCallback?: boolean; // Auto-handle OAuth callback hash on init, default true
autoRedirectAfterLogin?: boolean; // Auto-consume ?redirect=... after any successful login, default true
}Example:
const authClient = createAuthClient({
onTokenExpired: () => (window.location.href = "/login"),
onUnauthorized: () => alert("Session expired"),
});Built-in Features (Zero Config):
- ✅ Auto environment detection (browser, WeChat Mini Program, SSR)
- ✅ Auto token refresh (5 minutes before expiry)
- ✅ Auto permission sync
- ✅ Auto anonymous support
- ✅ Smart storage (localStorage, wx.storage, no-op for SSR)
Authentication
Register
await authClient.register({
email: "[email protected]",
password: "Password@123",
displayName: "John Doe",
});With Captcha:
// 1. Get captcha
const captchaData = await authClient.getCaptcha();
document.getElementById("captcha-img").src = captchaData.data.captchaImage;
// 2. Register with captcha
const userInputCode = "AB12";
await authClient.register({
email: "[email protected]",
password: "Password@123",
captcha: `${captchaData.data.captchaId}:${userInputCode}`,
});Login
Password Login:
await authClient.login({
loginType: "email",
email: "[email protected]",
password: "Password@123",
});Verification Code Login:
// 1. Send code
await authClient.sendCode({
type: "email",
email: "[email protected]",
});
// 2. Login with code
await authClient.loginWithCode({
loginType: "email",
email: "[email protected]",
code: "123456",
});OAuth Login
Full-page redirect with manual callback handling:
const authClient = createAuthClient({
autoHandleOAuthCallback: false,
});
// Login page
sessionStorage.setItem("oauth_redirect", "/dashboard");
authClient.loginWithOAuth("google");
// Callback page (/auth/callback)
const result = await authClient.handleOAuthCallback();
if (result.error) {
console.error(result.error.message);
return;
}
const redirectUrl = sessionStorage.getItem("oauth_redirect") || "/";
window.location.href = redirectUrl;OAuth callbacks are handled automatically by default. Do not also call
handleOAuthCallback() unless the client was created with
autoHandleOAuthCallback: false. Repeated calls are instance-idempotent, but
choosing one mode keeps callback ownership explicit.
If the callback page URL already carries ?redirect=... such as
/login?redirect=%2Fapi%2Foauth%2Fauthorize..., the SDK will reuse that
redirect target automatically after a successful OAuth callback. Both
#access_token=... and #accessToken=... callback hash formats are supported.
By default, the same ?redirect=... handling is also applied to password login,
verification-code login, registration auto-login, and mini-program login. Set
autoRedirectAfterLogin: false to disable this behavior and fully control
navigation in the application layer.
When the SDK actually consumes a redirect target, successful login results will
include result.data.redirectHandled === true and result.data.redirectTarget.
Applications that still run their own post-login navigation should short-circuit
on that flag to avoid double redirects.
Application-scoped Feishu login:
// The SDK uses requestAccess in the Feishu client and regular OAuth elsewhere.
// Pass a local path to restore the page that initiated login.
const result = await authClient.loginWithFeishu("/dashboard");
// Browser OAuth returns null because navigation has started. The callback page
// exchanges the short-lived, one-time login_ticket automatically.
if (result?.error) {
console.error(result.error.message);
}
// /auth/callback, when autoHandleOAuthCallback is false
const callbackResult = await authClient.handleOAuthCallback();The Feishu App ID and App Secret belong to the current application environment,
not to the Platform process. The SDK only receives the public App ID. The App
Secret stays in Runtime, and a browser callback carries a 60-second one-time
ticket instead of an application access token. Inside Feishu, the SDK waits for
window.h5sdk.ready() before requesting a code. If the client does not support
requestAccess, the SDK falls back to requestAuthCode.
Applications that already obtain a Feishu code may call
loginWithFeishuCode(code, returnUrl). A callback can also explicitly call
exchangeOAuthLoginTicket(ticket), although handleOAuthCallback() normally
handles this automatically.
Popup window:
// Main page
const popup = window.open("/api/auth/oauth/google", "oauth-login", "width=600,height=700");
window.addEventListener("message", (event) => {
if (event.data.type === "oauth-success") {
authClient.setAccessToken(event.data.accessToken);
popup?.close();
}
});
// Callback page
if (window.opener) {
const result = await authClient.handleOAuthCallback();
if (result.data?.accessToken) {
window.opener.postMessage(
{ type: "oauth-success", accessToken: result.data.accessToken },
origin
);
}
}Mini Program Login
The SDK exposes a unified Mini Program API for both WeChat and Douyin.
- WeChat runtime: auto-detects
wxand uses WeChat endpoints - Douyin Mini Program: auto-detects the
ttruntime and uses Douyin endpoints - Non-runtime or mixed environments: you can explicitly pass
platform
The SDK automatically detects the Mini Program storage runtime and uses WeChat wx.storage or Douyin tt.storage.
For most app code, prefer the high-level methods:
miniLogin()- automatically callsTaro.login/ Douyintt.login/ WeChatwx.login, then completes backend loginminiGetPhone(...)- accepts a raw code or event object, extracts the phone code, then binds the phone number
import { createAuthClient } from "@amaster.ai/auth-client";
import Taro from "@tarojs/taro";
// Zero configuration - auto-detects mini-program environment
const authClient = createAuthClient();
const result = await authClient.miniLogin();
if (result.data) {
console.log("Logged in:", result.data.user);
// Token automatically saved to mini-program storage
Taro.switchTab({ url: "/pages/index/index" });
} else if (result.error) {
Taro.showToast({
title: result.error.message || "Login failed",
icon: "none",
});
}Login with Douyin Mini Program:
const result = await authClient.miniLogin({
platform: "douyin",
});
if (result.data) {
console.log("Logged in:", result.data.user);
} else if (result.error) {
Taro.showToast({
title: result.error.message || "Login failed",
icon: "none",
});
}Get user phone number (optional):
<!-- WXML -->
<button
open-type="getPhoneNumber"
bindgetphonenumber="onGetPhoneNumber"
>
Get Phone Number
</button>// JS
async onGetPhoneNumber(e) {
const result = await authClient.miniGetPhone(e);
if (result.data) {
console.log("Phone number:", result.data.phone);
console.log("Verified:", result.data.phoneVerified);
// Update UI with phone number
this.setData({
phone: result.data.phone
});
} else if (result.error) {
Taro.showToast({
title: result.error.message || 'Failed to get phone number',
icon: 'none'
});
} else {
// User denied authorization
console.log("User cancelled phone number authorization");
}
}Get Douyin Mini Program phone number:
async function onGetPhoneNumber(e) {
const result = await authClient.miniGetPhone({
...e,
platform: "douyin",
});
if (result.data) {
console.log("Phone number:", result.data.phone);
}
}Complete Mini Program example:
// pages/login/login.js
import { createAuthClient } from "@amaster.ai/auth-client";
import Taro from "@tarojs/taro";
const authClient = createAuthClient();
Page({
data: {
userInfo: null,
hasPhone: false,
},
// Auto-login on page load
onLoad() {
this.handleLogin();
},
// WeChat Mini Program login
async handleLogin() {
Taro.showLoading({ title: "Logging in..." });
const result = await authClient.miniLogin();
Taro.hideLoading();
if (result.data) {
this.setData({
userInfo: result.data.user,
});
// Navigate to home
Taro.switchTab({ url: "/pages/index/index" });
} else {
Taro.showToast({
title: "Login failed",
icon: "none",
});
}
},
// Get phone number with user authorization
async onGetPhoneNumber(e) {
const { code } = e.detail;
if (!code) {
Taro.showToast({
title: "Authorization cancelled",
icon: "none",
});
return;
}
Taro.showLoading({ title: "Getting phone..." });
const result = await authClient.miniGetPhone(e);
Taro.hideLoading();
if (result.data) {
this.setData({
hasPhone: true,
});
Taro.showToast({
title: "Phone number obtained",
icon: "success",
});
} else {
Taro.showToast({
title: result.error?.message || "Failed to get phone",
icon: "none",
});
}
},
});Low-level compatibility APIs (deprecated, advanced use only):
If your app already has a login code or phone code, you can still call:
loginWithMiniProgram(code | { code, platform })getMiniProgramPhoneNumber(code | { code, platform })
Logout
await authClient.logout();User Management
Get Current User
const result = await authClient.getMe();
if (result.data) {
console.log(result.data); // User object
}Update Profile
await authClient.updateMe({
displayName: "New Name",
avatarUrl: "https://example.com/avatar.jpg",
});Change Password
await authClient.changePassword({
oldPassword: "OldPassword@123",
newPassword: "NewPassword@123",
});Permission Checks
Anonymous Access Support
The SDK automatically supports anonymous users with zero configuration:
// Create client
const authClient = createAuthClient();
// Permission checks work for both authenticated and anonymous users
if (authClient.hasPermission("article", "read")) {
showArticleList();
}
// Check if user is anonymous
if (authClient.isAnonymous()) {
showLoginPrompt();
}
// After login, permissions automatically update
await authClient.login({ ... });Local Permission Checks (Fast)
Permissions are cached locally for fast UI checks:
// Check role
if (authClient.hasRole("admin")) {
showAdminPanel();
}
if (authClient.isAnonymous()) {
showLoginPrompt();
}
// Check permission
if (authClient.hasPermission("user", "read")) {
showUserList();
}
## OAuth Bindings
### Get Bindings
```typescript
const result = await authClient.getOAuthBindings();
if (result.data) {
console.log(result.data); // Array of OAuthBinding
}
```
### Bind OAuth Account
```typescript
authClient.bindOAuth("google"); // Redirects to OAuth flow
```
### Unbind OAuth Account
```typescript
await authClient.unbindOAuth("google");
```
## Session Management
### Get Sessions
```typescript
const result = await authClient.getSessions();
if (result.data) {
result.data.forEach((session) => {
console.log(session.ip, session.userAgent, session.isCurrent);
});
}
```
### Revoke Session
```typescript
await authClient.revokeSession("session-id");
```
### Revoke All Sessions
```typescript
const result = await authClient.revokeAllSessions();
if (result.data) {
console.log(`Revoked ${result.data.revokedCount} sessions`);
}
```
## Event System
### Available Events
- `login` - User logged in, callback: `(user: User) => void`
- `logout` - User logged out, callback: `() => void`
- `tokenExpired` - Token expired, callback: `() => void`
- `tokenRefreshed` - Token refreshed, callback: `(token: string) => void`
- `unauthorized` - Unauthorized (401), callback: `() => void`
### Usage
```typescript
// Subscribe to events
authClient.on("unauthorized", () => {
authClient.clearAuth();
window.location.href = "/login";
});
authClient.on("login", (user) => {
console.log("User logged in:", user.displayName);
});
authClient.on("tokenRefreshed", (token) => {
console.log("Token refreshed");
});
// Unsubscribe
const handler = () => console.log("Logged out");
authClient.on("logout", handler);
authClient.off("logout", handler);
```
## Utility Methods
### Check Authentication Status
```typescript
if (authClient.isAuthenticated()) {
// User is logged in
}
```
### Get Access Token
```typescript
const token = authClient.getAccessToken();
```
### Set Access Token
```typescript
authClient.setAccessToken("your-token-here");
```
### Clear Auth Data
```typescript
authClient.clearAuth(); // Clears token and user data
```
## Error Handling
```typescript
try {
await authClient.login({ ... });
} catch (error) {
// Network error or exception
console.error("Login failed:", error);
}
// Or use result pattern
const result = await authClient.login({ ... });
if (result.error) {
// API error response
if (result.error.status === 401) {
alert("Invalid email or password");
} else if (result.error.status === 403) {
alert("Account is disabled");
} else {
alert(`Login failed: ${result.error.message}`);
}
}
```
## Best Practices
### Token Management
- Tokens are automatically managed by the SDK
- Access Token refreshes 5 minutes before expiration
- No manual token handling required
### Permission Checks
- Use permission checks for UI control (show/hide buttons, menus)
- Frontend checks are NOT security measures
- Backend must always verify permissions
### SSR Support
The SDK works in both browser and SSR environments:
```typescript
const authClient = createAuthClient();
```
## TypeScript
Full TypeScript support:
```typescript
import type {
User,
LoginResponse,
Session,
OAuthBinding,
} from "@amaster.ai/auth-client";
```
## License
MIT
## Contributing
Contributions are welcome! Please read our contributing guidelines before submitting PRs.