@amaster.ai/auth-client
v1.1.1
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({
baseURL: "https://api.example.com",
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 (recommended):
// Login page
sessionStorage.setItem("oauth_redirect", "/dashboard");
authClient.loginWithOAuth("google");
// Callback page (/auth/callback)
const { user } = await authClient.handleOAuthCallback();
const redirectUrl = sessionStorage.getItem("oauth_redirect") || "/";
window.location.href = redirectUrl;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.
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 { accessToken } = await authClient.handleOAuthCallback();
window.opener.postMessage({ type: "oauth-success", accessToken }, origin);
}WeChat Mini Program Login
The SDK automatically detects WeChat Mini Program environment and uses wx.storage:
import { createAuthClient } from "@amaster.ai/auth-client";
// Zero configuration - auto-detects WeChat environment
const authClient = createAuthClient({
baseURL: "https://api.yourdomain.com",
});
// Login with WeChat code
wx.login({
success: async (res) => {
if (res.code) {
const result = await authClient.loginWithMiniProgram(res.code);
if (result.data) {
console.log("Logged in:", result.data.user);
// Token automatically saved to wx.storage
wx.switchTab({ url: "/pages/index/index" });
} else if (result.error) {
wx.showToast({
title: result.error.message || "Login failed",
icon: "none",
});
}
}
},
fail: (err) => {
console.error("wx.login failed:", err);
},
});Get user phone number (optional):
<!-- WXML -->
<button
open-type="getPhoneNumber"
bindgetphonenumber="onGetPhoneNumber"
>
Get Phone Number
</button>// JS
async onGetPhoneNumber(e) {
const { code } = e.detail;
if (code) {
const result = await authClient.getMiniProgramPhoneNumber(code);
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) {
wx.showToast({
title: result.error.message || 'Failed to get phone number',
icon: 'none'
});
}
} else {
// User denied authorization
console.log("User cancelled phone number authorization");
}
}Complete Mini Program example:
// pages/login/login.js
import { createAuthClient } from "@amaster.ai/auth-client";
const authClient = createAuthClient({
baseURL: "https://api.yourdomain.com",
});
Page({
data: {
userInfo: null,
hasPhone: false,
},
// Auto-login on page load
onLoad() {
this.handleLogin();
},
// WeChat Mini Program login
async handleLogin() {
wx.showLoading({ title: "Logging in..." });
wx.login({
success: async (res) => {
if (res.code) {
const result = await authClient.loginWithMiniProgram(res.code);
wx.hideLoading();
if (result.data) {
this.setData({
userInfo: result.data.user,
});
// Navigate to home
wx.switchTab({ url: "/pages/index/index" });
} else {
wx.showToast({
title: "Login failed",
icon: "none",
});
}
}
},
fail: () => {
wx.hideLoading();
wx.showToast({
title: "Login failed",
icon: "none",
});
},
});
},
// Get phone number with user authorization
async onGetPhoneNumber(e) {
const { code } = e.detail;
if (!code) {
wx.showToast({
title: "Authorization cancelled",
icon: "none",
});
return;
}
wx.showLoading({ title: "Getting phone..." });
const result = await authClient.getMiniProgramPhoneNumber(code);
wx.hideLoading();
if (result.data) {
this.setData({
hasPhone: true,
});
wx.showToast({
title: "Phone number obtained",
icon: "success",
});
} else {
wx.showToast({
title: result.error?.message || "Failed to get phone",
icon: "none",
});
}
},
});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.