@55387.ai/uniauth-server 1.2.0 β†’ 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/INTEGRATION.md ADDED
@@ -0,0 +1,1273 @@
1
+ # UniAuth Integration Guide for AI Assistants
2
+
3
+ > **Purpose**: This document provides everything an AI coding assistant needs to integrate UniAuth into any project. It covers all authentication methods, SDK usage, OAuth2/OIDC flows, API reference, and complete code examples.
4
+ >
5
+ > **UniAuth Service URL (Production)**: `https://sso.55387.xyz`
6
+ >
7
+ > **SDK Packages**:
8
+ > - Frontend: `@55387.ai/uniauth-client` (v1.2.0)
9
+ > - Backend: `@55387.ai/uniauth-server` (v1.2.0)
10
+
11
+ ---
12
+
13
+ ## Table of Contents
14
+
15
+ 1. [Overview](#1-overview)
16
+ 2. [Prerequisites](#2-prerequisites)
17
+ 3. [Integration Methods Decision Tree](#3-integration-methods-decision-tree)
18
+ 4. [Method A: SDK Integration (Recommended)](#4-method-a-sdk-integration-recommended)
19
+ 5. [Method B: SSO / OAuth2 Authorization Code Flow](#5-method-b-sso--oauth2-authorization-code-flow)
20
+ 6. [Method C: OIDC Standard Integration](#6-method-c-oidc-standard-integration)
21
+ 7. [Method D: Direct API Integration (No SDK)](#7-method-d-direct-api-integration-no-sdk)
22
+ 8. [Complete API Reference](#8-complete-api-reference)
23
+ 9. [Data Types & Interfaces](#9-data-types--interfaces)
24
+ 10. [Error Handling](#10-error-handling)
25
+ 11. [Security Best Practices](#11-security-best-practices)
26
+ 12. [Framework-Specific Examples](#12-framework-specific-examples)
27
+ 13. [Troubleshooting](#13-troubleshooting)
28
+ 14. [FAQ](#14-faq)
29
+
30
+ ---
31
+
32
+ ## 1. Overview
33
+
34
+ UniAuth is a unified authentication platform providing centralized user authentication and authorization. It supports:
35
+
36
+ | Feature | Description |
37
+ |---------|-------------|
38
+ | πŸ“± Phone Login | Phone + SMS verification code (Tencent Cloud SMS) |
39
+ | πŸ“§ Email Login | Email + verification code / Email + password |
40
+ | πŸ” OAuth2/OIDC | Acts as an OAuth 2.0 / OpenID Connect Provider |
41
+ | πŸ”‘ JWT Tokens | Access Token (1h) + Refresh Token (30d) with rotation |
42
+ | πŸ”„ SSO | Single Sign-On across multiple applications |
43
+ | πŸ›‘οΈ MFA | TOTP-based multi-factor authentication |
44
+ | πŸ€– M2M | Machine-to-Machine authentication via Client Credentials |
45
+ | πŸ”Œ Social Login | Google, GitHub, WeChat OAuth |
46
+
47
+ ### Architecture
48
+
49
+ ```
50
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
51
+ β”‚ Your Application β”‚
52
+ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
53
+ β”‚ β”‚Frontendβ”‚ β”‚ Backend β”‚ β”‚
54
+ β”‚ β”‚(Client β”‚ β”‚ (Server β”‚ β”‚
55
+ β”‚ β”‚ SDK) β”‚ β”‚ SDK) β”‚ β”‚
56
+ β”‚ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β”‚
57
+ β””β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”˜
58
+ β”‚ β”‚
59
+ β”‚ HTTPS β”‚ HTTPS
60
+ β–Ό β–Ό
61
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
62
+ β”‚ UniAuth Server β”‚
63
+ β”‚ https://sso.55387.xyz β”‚
64
+ β”‚ (Hono + Supabase + Redis) β”‚
65
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
66
+ ```
67
+
68
+ ---
69
+
70
+ ## 2. Prerequisites
71
+
72
+ ### 2.1 Register Your Application
73
+
74
+ Go to the **UniAuth Developer Console** (`https://sso.55387.xyz/developer`) and register your app to get:
75
+
76
+ | Credential | Description | Example |
77
+ |------------|-------------|---------|
78
+ | `Client ID` | Unique app identifier | `ua_xxxxxxxxxxxx` |
79
+ | `Client Secret` | App secret key (keep secure, never in frontend) | `xxxxxxxx` |
80
+ | `Redirect URIs` | Allowed OAuth callback URLs (multiple supported) | `http://localhost:3000/callback` |
81
+ | `Client Type` | **Public** (frontend SPA) or **Confidential** (backend) | Depends on your architecture |
82
+ | `Scopes` | Authorization scopes | `openid profile email phone` |
83
+
84
+ ### 2.2 Environment Variables
85
+
86
+ Your project needs these environment variables:
87
+
88
+ ```env
89
+ # Required
90
+ UNIAUTH_URL=https://sso.55387.xyz
91
+ UNIAUTH_CLIENT_ID=ua_xxxxxxxxxxxx
92
+ UNIAUTH_CLIENT_SECRET=your_client_secret # Backend only, NEVER in frontend
93
+
94
+ # Optional
95
+ UNIAUTH_REDIRECT_URI=http://localhost:3000/callback
96
+ ```
97
+
98
+ ---
99
+
100
+ ## 3. Integration Methods Decision Tree
101
+
102
+ Choose the best method based on your scenario:
103
+
104
+ ```
105
+ Is your app a Node.js / TypeScript project?
106
+ β”œβ”€β”€ YES β†’ Do you want embedded login UI (your own login form)?
107
+ β”‚ β”œβ”€β”€ YES β†’ Use Method A: SDK Integration (Trusted Client)
108
+ β”‚ └── NO β†’ Do you need SSO (redirect to UniAuth login page)?
109
+ β”‚ β”œβ”€β”€ YES β†’ Is your backend a Confidential Client?
110
+ β”‚ β”‚ β”œβ”€β”€ YES β†’ Use Method B: Backend-proxy SSO flow
111
+ β”‚ β”‚ └── NO β†’ Use Method A: Client SDK SSO
112
+ β”‚ └── NO β†’ Use Method A: SDK Integration
113
+ β”œβ”€β”€ NO β†’ Are you using Python, Java, Go, etc.?
114
+ β”‚ β”œβ”€β”€ YES β†’ Use Method C: OIDC Standard Integration
115
+ β”‚ └── NO β†’ Use Method D: Direct API Integration
116
+ ```
117
+
118
+ | Method | When to Use | Complexity |
119
+ |--------|-------------|------------|
120
+ | **A: SDK** | Node.js/TS projects, fastest integration | ⭐ Low |
121
+ | **B: SSO/OAuth2** | Cross-domain SSO, redirecting to UniAuth login page | ⭐⭐ Medium |
122
+ | **C: OIDC** | Non-Node.js projects, using standard OIDC libraries | ⭐⭐ Medium |
123
+ | **D: Direct API** | Any language, no SDK dependency, full control | ⭐⭐⭐ High |
124
+
125
+ ---
126
+
127
+ ## 4. Method A: SDK Integration (Recommended)
128
+
129
+ ### 4.1 Installation
130
+
131
+ ```bash
132
+ # Frontend SDK
133
+ npm install @55387.ai/uniauth-client
134
+
135
+ # Backend SDK
136
+ npm install @55387.ai/uniauth-server
137
+ ```
138
+
139
+ ---
140
+
141
+ ### 4.2 Frontend SDK β€” Embedded Login (Trusted Client)
142
+
143
+ > **Requirement**: Your app must be registered as a **Trusted Client** (`trusted_client` grant type) in the Developer Console.
144
+
145
+ #### Initialize
146
+
147
+ ```typescript
148
+ import { UniAuthClient } from '@55387.ai/uniauth-client';
149
+
150
+ const auth = new UniAuthClient({
151
+ baseUrl: 'https://sso.55387.xyz',
152
+ // clientId: 'ua_xxxx', // Optional for trusted client
153
+ // storage: 'localStorage', // 'localStorage' | 'sessionStorage' | 'memory'
154
+ // enableRetry: true, // Auto-retry on failure (default: true)
155
+ // timeout: 30000, // Request timeout in ms (default: 30000)
156
+ onTokenRefresh: (tokens) => {
157
+ console.log('Tokens refreshed automatically');
158
+ },
159
+ onAuthError: (error) => {
160
+ console.error('Auth error:', error);
161
+ },
162
+ });
163
+ ```
164
+
165
+ #### Phone + SMS Login
166
+
167
+ ```typescript
168
+ // Step 1: Send verification code
169
+ const sendResult = await auth.sendCode('+8613800138000');
170
+ // sendResult: { success: true, data: { expires_in: 300, retry_after: 60 } }
171
+
172
+ // Step 2: Login with code
173
+ const loginResult = await auth.loginWithCode('+8613800138000', '123456');
174
+ // loginResult: { success: true, data: { user, access_token, refresh_token, expires_in, is_new_user } }
175
+ ```
176
+
177
+ #### Email + Code Login (Passwordless)
178
+
179
+ ```typescript
180
+ // Step 1: Send email code
181
+ await auth.sendEmailCode('user@example.com');
182
+
183
+ // Step 2: Login
184
+ const result = await auth.loginWithEmailCode('user@example.com', '123456');
185
+ ```
186
+
187
+ #### Email + Password Login
188
+
189
+ ```typescript
190
+ const result = await auth.loginWithEmail('user@example.com', 'password123');
191
+ ```
192
+
193
+ #### Email Registration
194
+
195
+ ```typescript
196
+ const result = await auth.registerWithEmail('user@example.com', 'password123', 'Nickname');
197
+ ```
198
+
199
+ #### MFA (Multi-Factor Authentication)
200
+
201
+ ```typescript
202
+ const result = await auth.loginWithCode(phone, code);
203
+
204
+ if (result.mfa_required) {
205
+ // Prompt user for TOTP code from authenticator app
206
+ const mfaCode = '123456';
207
+ const mfaResult = await auth.verifyMFA(result.mfa_token!, mfaCode);
208
+ // mfaResult contains final access_token and refresh_token
209
+ }
210
+ ```
211
+
212
+ #### User Management
213
+
214
+ ```typescript
215
+ // Get current user
216
+ const user = await auth.getCurrentUser();
217
+ // user: { id, phone, email, nickname, avatar_url }
218
+
219
+ // Update profile
220
+ await auth.updateProfile({ nickname: 'New Name', avatar_url: 'https://...' });
221
+
222
+ // Check auth status
223
+ const isLoggedIn = auth.isAuthenticated();
224
+
225
+ // Get access token (auto-refreshes if expired)
226
+ const token = await auth.getAccessToken();
227
+
228
+ // Get cached user (synchronous, no API call)
229
+ const cachedUser = auth.getCachedUser();
230
+
231
+ // Listen to auth state changes
232
+ const unsubscribe = auth.onAuthStateChange((user, isAuthenticated) => {
233
+ if (isAuthenticated) {
234
+ console.log('Logged in:', user);
235
+ } else {
236
+ console.log('Logged out');
237
+ }
238
+ });
239
+
240
+ // Logout
241
+ await auth.logout(); // Current device
242
+ await auth.logoutAll(); // All devices
243
+ ```
244
+
245
+ #### Social Login
246
+
247
+ ```typescript
248
+ // Get available OAuth providers
249
+ const providers = await auth.getOAuthProviders();
250
+ // providers: ['google', 'github', 'wechat']
251
+
252
+ // Start social login (redirects user)
253
+ auth.startSocialLogin('google');
254
+ auth.startSocialLogin('github');
255
+ auth.startSocialLogin('wechat');
256
+ ```
257
+
258
+ ---
259
+
260
+ ### 4.3 Frontend SDK β€” SSO Login (Public Client)
261
+
262
+ > For apps that redirect to UniAuth's login page instead of building their own.
263
+
264
+ ```typescript
265
+ // Step 1: Configure SSO
266
+ auth.configureSso({
267
+ ssoUrl: 'https://sso.55387.xyz',
268
+ clientId: 'ua_xxxxxxxxxxxx',
269
+ redirectUri: window.location.origin + '/callback',
270
+ scope: 'openid profile email phone', // default: 'openid profile email'
271
+ });
272
+
273
+ // Step 2: Trigger SSO login (redirects to UniAuth login page)
274
+ auth.loginWithSSO();
275
+
276
+ // With PKCE (recommended for public clients):
277
+ auth.loginWithSSO({ usePKCE: true });
278
+ ```
279
+
280
+ ```typescript
281
+ // Step 3: Handle callback (on your /callback page)
282
+ // React example:
283
+ function CallbackPage() {
284
+ useEffect(() => {
285
+ const handleCallback = async () => {
286
+ if (auth.isSSOCallback()) {
287
+ try {
288
+ const result = await auth.handleSSOCallback();
289
+ if (result) {
290
+ // result: { access_token, refresh_token?, expires_in?, token_type, id_token? }
291
+ localStorage.setItem('access_token', result.access_token);
292
+ if (result.refresh_token) {
293
+ localStorage.setItem('refresh_token', result.refresh_token);
294
+ }
295
+ window.location.href = '/';
296
+ }
297
+ } catch (error) {
298
+ console.error('SSO callback error:', error);
299
+ }
300
+ }
301
+ };
302
+ handleCallback();
303
+ }, []);
304
+
305
+ return <div>Logging in...</div>;
306
+ }
307
+ ```
308
+
309
+ > ⚠️ **Important**: If your app is a **Confidential Client**, the frontend SDK cannot call the token endpoint directly (missing `client_secret`). Use **Method B** (backend-proxy flow) instead.
310
+
311
+ ---
312
+
313
+ ### 4.4 Backend SDK
314
+
315
+ #### Initialize
316
+
317
+ ```typescript
318
+ // lib/auth.ts
319
+ import { UniAuthServer } from '@55387.ai/uniauth-server';
320
+
321
+ export const uniauth = new UniAuthServer({
322
+ baseUrl: process.env.UNIAUTH_URL || 'https://sso.55387.xyz',
323
+ clientId: process.env.UNIAUTH_CLIENT_ID!,
324
+ clientSecret: process.env.UNIAUTH_CLIENT_SECRET!,
325
+ // jwtPublicKey: '...', // Optional: for local JWT verification (faster)
326
+ });
327
+ ```
328
+
329
+ #### Express Middleware
330
+
331
+ ```typescript
332
+ import express from 'express';
333
+ import { uniauth } from './lib/auth';
334
+
335
+ const app = express();
336
+
337
+ // Protect all /api routes
338
+ app.use('/api/*', uniauth.middleware());
339
+
340
+ // Access user info in routes
341
+ app.get('/api/profile', (req, res) => {
342
+ // req.user β€” full user info (UserInfo)
343
+ // req.authPayload β€” JWT payload (TokenPayload)
344
+ res.json({
345
+ userId: req.user?.id,
346
+ email: req.user?.email,
347
+ phone: req.user?.phone,
348
+ tokenExp: req.authPayload?.exp,
349
+ });
350
+ });
351
+ ```
352
+
353
+ #### Hono Middleware
354
+
355
+ ```typescript
356
+ import { Hono } from 'hono';
357
+ import { uniauth } from './lib/auth';
358
+
359
+ const app = new Hono();
360
+
361
+ // Protect all /api routes
362
+ app.use('/api/*', uniauth.honoMiddleware());
363
+
364
+ // Access user info
365
+ app.get('/api/profile', (c) => {
366
+ const user = c.get('user');
367
+ return c.json({ user });
368
+ });
369
+ ```
370
+
371
+ #### Manual Token Verification
372
+
373
+ ```typescript
374
+ import { uniauth } from './lib/auth';
375
+
376
+ async function handleRequest(req: Request) {
377
+ const token = req.headers.get('Authorization')?.replace('Bearer ', '');
378
+
379
+ if (!token) {
380
+ return new Response('Unauthorized', { status: 401 });
381
+ }
382
+
383
+ try {
384
+ const payload = await uniauth.verifyToken(token);
385
+ // payload.sub = User ID
386
+ // payload.email = Email
387
+ // payload.phone = Phone
388
+ // payload.exp = Expiration (Unix timestamp)
389
+ } catch (error) {
390
+ return new Response('Invalid token', { status: 401 });
391
+ }
392
+ }
393
+ ```
394
+
395
+ #### Token Introspection (RFC 7662)
396
+
397
+ ```typescript
398
+ const result = await uniauth.introspectToken(accessToken);
399
+
400
+ if (result.active) {
401
+ console.log('User:', result.sub);
402
+ console.log('Scopes:', result.scope);
403
+ } else {
404
+ console.log('Token is invalid or expired');
405
+ }
406
+ ```
407
+
408
+ #### Quick Token Check
409
+
410
+ ```typescript
411
+ const isValid = await uniauth.isTokenActive(token);
412
+ ```
413
+
414
+ ---
415
+
416
+ ## 5. Method B: SSO / OAuth2 Authorization Code Flow
417
+
418
+ ### When to Use
419
+
420
+ Use this when your backend is a **Confidential Client** and you want SSO (redirect to UniAuth login page with `client_secret` exchange on the backend).
421
+
422
+ ### Flow Diagram
423
+
424
+ ```
425
+ User β†’ Frontend β†’ /api/auth/login β†’ Backend generates auth URL β†’ Redirect to SSO
426
+ ↓
427
+ User ← Frontend ← / ← Backend sets Cookie ← SSO callback to /api/auth/callback
428
+ ↑
429
+ Backend exchanges code for tokens with client_secret
430
+ ```
431
+
432
+ ### OAuth2 Endpoints
433
+
434
+ | Endpoint | URL |
435
+ |----------|-----|
436
+ | Authorization | `https://sso.55387.xyz/api/v1/oauth2/authorize` |
437
+ | Token | `https://sso.55387.xyz/api/v1/oauth2/token` |
438
+ | UserInfo | `https://sso.55387.xyz/api/v1/oauth2/userinfo` |
439
+ | JWKS | `https://sso.55387.xyz/.well-known/jwks.json` |
440
+ | OIDC Discovery | `https://sso.55387.xyz/.well-known/openid-configuration` |
441
+
442
+ ### Complete Backend Implementation (Hono)
443
+
444
+ ```typescript
445
+ import { Hono } from 'hono';
446
+ import { setCookie, getCookie, deleteCookie } from 'hono/cookie';
447
+ import { UniAuthServer } from '@55387.ai/uniauth-server';
448
+ import crypto from 'crypto';
449
+
450
+ const app = new Hono();
451
+
452
+ const auth = new UniAuthServer({
453
+ baseUrl: 'https://sso.55387.xyz',
454
+ clientId: process.env.UNIAUTH_CLIENT_ID!,
455
+ clientSecret: process.env.UNIAUTH_CLIENT_SECRET!,
456
+ });
457
+
458
+ // Helper: generate random state for CSRF protection
459
+ function generateState(): string {
460
+ return crypto.randomBytes(32).toString('hex');
461
+ }
462
+
463
+ // 1. Login endpoint β€” redirects to UniAuth SSO
464
+ app.get('/api/auth/login', (c) => {
465
+ const origin = c.req.header('origin') || c.req.header('referer')?.replace(/\/+$/, '') || 'http://localhost:3000';
466
+ const redirectUri = `${origin}/api/auth/callback`;
467
+ const state = generateState();
468
+
469
+ // TODO: Store state in Redis/session for CSRF validation
470
+
471
+ const params = new URLSearchParams({
472
+ client_id: process.env.UNIAUTH_CLIENT_ID!,
473
+ redirect_uri: redirectUri,
474
+ response_type: 'code',
475
+ scope: 'openid profile email phone',
476
+ state,
477
+ });
478
+
479
+ return c.redirect(`https://sso.55387.xyz/api/v1/oauth2/authorize?${params.toString()}`);
480
+ });
481
+
482
+ // 2. Callback endpoint β€” exchanges code for tokens
483
+ app.get('/api/auth/callback', async (c) => {
484
+ const code = c.req.query('code');
485
+ const state = c.req.query('state');
486
+
487
+ if (!code) {
488
+ return c.json({ error: 'Missing authorization code' }, 400);
489
+ }
490
+
491
+ // TODO: Validate state against stored value
492
+
493
+ const origin = c.req.header('referer')?.replace(/\/api\/auth\/callback.*$/, '') || 'http://localhost:3000';
494
+ const redirectUri = `${origin}/api/auth/callback`;
495
+
496
+ // Exchange code for tokens
497
+ const response = await fetch('https://sso.55387.xyz/api/v1/oauth2/token', {
498
+ method: 'POST',
499
+ headers: { 'Content-Type': 'application/json' },
500
+ body: JSON.stringify({
501
+ client_id: process.env.UNIAUTH_CLIENT_ID,
502
+ client_secret: process.env.UNIAUTH_CLIENT_SECRET,
503
+ code,
504
+ grant_type: 'authorization_code',
505
+ redirect_uri: redirectUri,
506
+ }),
507
+ });
508
+
509
+ if (!response.ok) {
510
+ const error = await response.json();
511
+ return c.json({ error: 'Token exchange failed', details: error }, 400);
512
+ }
513
+
514
+ const { access_token, id_token, refresh_token } = await response.json();
515
+
516
+ // Store token in httpOnly cookie (secure!)
517
+ setCookie(c, 'auth_token', id_token || access_token, {
518
+ httpOnly: true,
519
+ secure: true,
520
+ sameSite: 'Lax',
521
+ maxAge: 60 * 60 * 24 * 7, // 7 days
522
+ path: '/',
523
+ });
524
+
525
+ // Redirect to frontend home
526
+ return c.redirect('/');
527
+ });
528
+
529
+ // 3. Auth status endpoint
530
+ app.get('/api/auth/status', async (c) => {
531
+ const token = getCookie(c, 'auth_token');
532
+ if (!token) {
533
+ return c.json({ authenticated: false });
534
+ }
535
+
536
+ try {
537
+ const payload = await auth.verifyToken(token);
538
+ return c.json({
539
+ authenticated: true,
540
+ userId: payload.sub,
541
+ email: payload.email,
542
+ });
543
+ } catch {
544
+ return c.json({ authenticated: false });
545
+ }
546
+ });
547
+
548
+ // 4. Logout endpoint
549
+ app.post('/api/auth/logout', (c) => {
550
+ deleteCookie(c, 'auth_token', { path: '/' });
551
+ return c.json({ success: true });
552
+ });
553
+ ```
554
+
555
+ ### Frontend Code for SSO (Non-SDK)
556
+
557
+ ```typescript
558
+ // Trigger login
559
+ const handleLogin = () => {
560
+ window.location.href = '/api/auth/login';
561
+ };
562
+
563
+ // Check login status
564
+ const checkAuth = async (): Promise<boolean> => {
565
+ const response = await fetch('/api/auth/status', { credentials: 'include' });
566
+ const data = await response.json();
567
+ return data.authenticated === true;
568
+ };
569
+
570
+ // Logout
571
+ const handleLogout = async () => {
572
+ await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' });
573
+ window.location.href = '/login';
574
+ };
575
+ ```
576
+
577
+ ---
578
+
579
+ ## 6. Method C: OIDC Standard Integration
580
+
581
+ UniAuth is a **fully OIDC-compliant Identity Provider**. Any standard OIDC client library works.
582
+
583
+ ### OIDC Discovery URL
584
+
585
+ ```
586
+ https://sso.55387.xyz/.well-known/openid-configuration
587
+ ```
588
+
589
+ ### Discovery Response (Key Fields)
590
+
591
+ ```json
592
+ {
593
+ "issuer": "https://sso.55387.xyz",
594
+ "authorization_endpoint": "https://sso.55387.xyz/oauth2/authorize",
595
+ "token_endpoint": "https://sso.55387.xyz/api/v1/oauth2/token",
596
+ "userinfo_endpoint": "https://sso.55387.xyz/api/v1/oauth2/userinfo",
597
+ "jwks_uri": "https://sso.55387.xyz/.well-known/jwks.json",
598
+ "scopes_supported": ["openid", "profile", "email", "phone"],
599
+ "response_types_supported": ["code"],
600
+ "grant_types_supported": ["authorization_code", "client_credentials", "refresh_token"],
601
+ "id_token_signing_alg_values_supported": ["RS256"]
602
+ }
603
+ ```
604
+
605
+ ### Node.js + Passport
606
+
607
+ ```javascript
608
+ import passport from 'passport';
609
+ import { Strategy as OpenIDStrategy } from 'passport-openidconnect';
610
+
611
+ passport.use(new OpenIDStrategy({
612
+ issuer: 'https://sso.55387.xyz',
613
+ authorizationURL: 'https://sso.55387.xyz/oauth2/authorize',
614
+ tokenURL: 'https://sso.55387.xyz/api/v1/oauth2/token',
615
+ userInfoURL: 'https://sso.55387.xyz/api/v1/oauth2/userinfo',
616
+ clientID: process.env.UNIAUTH_CLIENT_ID,
617
+ clientSecret: process.env.UNIAUTH_CLIENT_SECRET,
618
+ callbackURL: 'https://myapp.com/callback',
619
+ scope: ['openid', 'profile', 'email']
620
+ },
621
+ (issuer, profile, done) => {
622
+ return done(null, profile);
623
+ }
624
+ ));
625
+
626
+ app.get('/auth/uniauth', passport.authenticate('openidconnect'));
627
+ app.get('/callback',
628
+ passport.authenticate('openidconnect', { failureRedirect: '/login' }),
629
+ (req, res) => res.redirect('/dashboard')
630
+ );
631
+ ```
632
+
633
+ ### Next.js + NextAuth
634
+
635
+ ```typescript
636
+ // pages/api/auth/[...nextauth].ts or app/api/auth/[...nextauth]/route.ts
637
+ import NextAuth from 'next-auth';
638
+
639
+ export const authOptions = {
640
+ providers: [
641
+ {
642
+ id: 'uniauth',
643
+ name: 'UniAuth',
644
+ type: 'oauth',
645
+ wellKnown: 'https://sso.55387.xyz/.well-known/openid-configuration',
646
+ authorization: { params: { scope: 'openid profile email phone' } },
647
+ idToken: true,
648
+ profile(profile) {
649
+ return {
650
+ id: profile.sub,
651
+ name: profile.name,
652
+ email: profile.email,
653
+ image: profile.picture,
654
+ };
655
+ },
656
+ clientId: process.env.UNIAUTH_CLIENT_ID,
657
+ clientSecret: process.env.UNIAUTH_CLIENT_SECRET,
658
+ },
659
+ ],
660
+ };
661
+
662
+ export default NextAuth(authOptions);
663
+ ```
664
+
665
+ ### Python + Authlib (Flask)
666
+
667
+ ```python
668
+ from authlib.integrations.flask_client import OAuth
669
+
670
+ oauth = OAuth(app)
671
+
672
+ uniauth = oauth.register(
673
+ 'uniauth',
674
+ client_id='ua_abc123',
675
+ client_secret='SECRET',
676
+ server_metadata_url='https://sso.55387.xyz/.well-known/openid-configuration',
677
+ client_kwargs={'scope': 'openid profile email'}
678
+ )
679
+
680
+ @app.route('/login')
681
+ def login():
682
+ redirect_uri = url_for('callback', _external=True)
683
+ return uniauth.authorize_redirect(redirect_uri)
684
+
685
+ @app.route('/callback')
686
+ def callback():
687
+ token = uniauth.authorize_access_token()
688
+ user_info = uniauth.parse_id_token(token)
689
+ # user_info['email'], user_info['name'], user_info['sub']
690
+ return redirect('/dashboard')
691
+ ```
692
+
693
+ ---
694
+
695
+ ## 7. Method D: Direct API Integration (No SDK)
696
+
697
+ ### 7.1 Phone SMS Login
698
+
699
+ ```bash
700
+ # Step 1: Send code
701
+ curl -X POST https://sso.55387.xyz/api/v1/auth/phone/send-code \
702
+ -H "Content-Type: application/json" \
703
+ -d '{"phone": "+8613800138000"}'
704
+
705
+ # Response: {"success": true, "data": {"expires_in": 300, "retry_after": 60}}
706
+
707
+ # Step 2: Verify code and login
708
+ curl -X POST https://sso.55387.xyz/api/v1/auth/phone/verify \
709
+ -H "Content-Type: application/json" \
710
+ -d '{"phone": "+8613800138000", "code": "123456"}'
711
+
712
+ # Response: {"success": true, "data": {"user": {...}, "access_token": "eyJ...", "refresh_token": "...", "expires_in": 3600}}
713
+ ```
714
+
715
+ ### 7.2 Email Login
716
+
717
+ ```bash
718
+ # Email + Code
719
+ curl -X POST https://sso.55387.xyz/api/v1/auth/email/send-code \
720
+ -H "Content-Type: application/json" \
721
+ -d '{"email": "user@example.com", "type": "login"}'
722
+
723
+ curl -X POST https://sso.55387.xyz/api/v1/auth/email/verify \
724
+ -H "Content-Type: application/json" \
725
+ -d '{"email": "user@example.com", "code": "123456"}'
726
+
727
+ # Email + Password
728
+ curl -X POST https://sso.55387.xyz/api/v1/auth/email/login \
729
+ -H "Content-Type: application/json" \
730
+ -d '{"email": "user@example.com", "password": "password123"}'
731
+
732
+ # Email Registration
733
+ curl -X POST https://sso.55387.xyz/api/v1/auth/email/register \
734
+ -H "Content-Type: application/json" \
735
+ -d '{"email": "user@example.com", "password": "password123", "code": "123456"}'
736
+ ```
737
+
738
+ ### 7.3 Token Management
739
+
740
+ ```bash
741
+ # Refresh token
742
+ curl -X POST https://sso.55387.xyz/api/v1/auth/refresh \
743
+ -H "Content-Type: application/json" \
744
+ -d '{"refresh_token": "your_refresh_token"}'
745
+
746
+ # Verify token
747
+ curl -X POST https://sso.55387.xyz/api/v1/auth/verify \
748
+ -H "Content-Type: application/json" \
749
+ -d '{"token": "your_access_token"}'
750
+
751
+ # Logout
752
+ curl -X POST https://sso.55387.xyz/api/v1/auth/logout \
753
+ -H "Authorization: Bearer your_access_token" \
754
+ -H "Content-Type: application/json" \
755
+ -d '{"refresh_token": "your_refresh_token"}'
756
+
757
+ # Logout all devices
758
+ curl -X POST https://sso.55387.xyz/api/v1/auth/logout-all \
759
+ -H "Authorization: Bearer your_access_token"
760
+ ```
761
+
762
+ ### 7.4 User Info
763
+
764
+ ```bash
765
+ # Get current user
766
+ curl https://sso.55387.xyz/api/v1/user/me \
767
+ -H "Authorization: Bearer your_access_token"
768
+
769
+ # Update profile
770
+ curl -X PATCH https://sso.55387.xyz/api/v1/user/me \
771
+ -H "Authorization: Bearer your_access_token" \
772
+ -H "Content-Type: application/json" \
773
+ -d '{"nickname": "NewName", "avatar_url": "https://..."}'
774
+ ```
775
+
776
+ ### 7.5 OAuth2 Authorization Code Flow (cURL)
777
+
778
+ ```bash
779
+ # Step 1: Redirect user to (open in browser)
780
+ https://sso.55387.xyz/api/v1/oauth2/authorize?client_id=ua_abc123&redirect_uri=https://myapp.com/callback&response_type=code&scope=openid%20profile%20email&state=random_string
781
+
782
+ # Step 2: Exchange code for tokens
783
+ curl -X POST https://sso.55387.xyz/api/v1/oauth2/token \
784
+ -H "Content-Type: application/x-www-form-urlencoded" \
785
+ -d "grant_type=authorization_code" \
786
+ -d "code=AUTH_CODE" \
787
+ -d "redirect_uri=https://myapp.com/callback" \
788
+ -d "client_id=ua_abc123" \
789
+ -d "client_secret=SECRET"
790
+
791
+ # Step 3: Get user info
792
+ curl https://sso.55387.xyz/api/v1/oauth2/userinfo \
793
+ -H "Authorization: Bearer ACCESS_TOKEN"
794
+
795
+ # Refresh OAuth2 tokens
796
+ curl -X POST https://sso.55387.xyz/api/v1/oauth2/token \
797
+ -d "grant_type=refresh_token" \
798
+ -d "refresh_token=REFRESH_TOKEN" \
799
+ -d "client_id=ua_abc123" \
800
+ -d "client_secret=SECRET"
801
+ ```
802
+
803
+ ---
804
+
805
+ ## 8. Complete API Reference
806
+
807
+ ### Authentication APIs
808
+
809
+ | Method | Endpoint | Auth Required | Description |
810
+ |--------|----------|:---:|-------------|
811
+ | POST | `/api/v1/auth/phone/send-code` | ❌ | Send phone SMS code |
812
+ | POST | `/api/v1/auth/phone/verify` | ❌ | Login with phone + code |
813
+ | POST | `/api/v1/auth/email/send-code` | ❌ | Send email verification code |
814
+ | POST | `/api/v1/auth/email/verify` | ❌ | Login with email + code |
815
+ | POST | `/api/v1/auth/email/login` | ❌ | Login with email + password |
816
+ | POST | `/api/v1/auth/email/register` | ❌ | Register with email + password |
817
+ | POST | `/api/v1/auth/refresh` | ❌ | Refresh access token |
818
+ | POST | `/api/v1/auth/verify` | ❌ | Verify a token |
819
+ | POST | `/api/v1/auth/logout` | βœ… | Logout current device |
820
+ | POST | `/api/v1/auth/logout-all` | βœ… | Logout all devices |
821
+ | GET | `/api/v1/auth/google` | ❌ | Google OAuth redirect |
822
+ | GET | `/api/v1/auth/github` | ❌ | GitHub OAuth redirect |
823
+ | GET | `/api/v1/auth/wechat` | ❌ | WeChat OAuth redirect |
824
+
825
+ ### User APIs
826
+
827
+ | Method | Endpoint | Auth Required | Description |
828
+ |--------|----------|:---:|-------------|
829
+ | GET | `/api/v1/user/me` | βœ… | Get current user |
830
+ | PATCH | `/api/v1/user/me` | βœ… | Update profile |
831
+ | GET | `/api/v1/user/sessions` | βœ… | Get active sessions |
832
+ | DELETE | `/api/v1/user/sessions/:id` | βœ… | Revoke a session |
833
+ | GET | `/api/v1/user/bindings` | βœ… | Get OAuth account bindings |
834
+ | DELETE | `/api/v1/user/unbind/:provider` | βœ… | Unbind OAuth account |
835
+ | POST | `/api/v1/user/bind/phone` | βœ… | Bind phone number |
836
+ | POST | `/api/v1/user/bind/email` | βœ… | Bind email |
837
+ | GET | `/api/v1/user/authorized-apps` | βœ… | Get authorized apps |
838
+ | DELETE | `/api/v1/user/authorized-apps/:clientId` | βœ… | Revoke app authorization |
839
+
840
+ ### MFA APIs
841
+
842
+ | Method | Endpoint | Auth Required | Description |
843
+ |--------|----------|:---:|-------------|
844
+ | GET | `/api/v1/mfa/status` | βœ… | Get MFA status |
845
+ | POST | `/api/v1/mfa/setup` | βœ… | Start MFA setup (returns QR code) |
846
+ | POST | `/api/v1/mfa/verify-setup` | βœ… | Confirm MFA setup |
847
+ | POST | `/api/v1/mfa/verify` | βœ… | Verify MFA code during login |
848
+ | POST | `/api/v1/mfa/disable` | βœ… | Disable MFA |
849
+ | POST | `/api/v1/mfa/regenerate-recovery` | βœ… | Regenerate recovery codes |
850
+
851
+ ### OAuth2 Provider APIs
852
+
853
+ | Method | Endpoint | Auth Required | Description |
854
+ |--------|----------|:---:|-------------|
855
+ | GET | `/api/v1/oauth2/validate` | ❌ | Validate client & redirect_uri |
856
+ | POST | `/api/v1/oauth2/authorize` | βœ… | Generate authorization code |
857
+ | POST | `/api/v1/oauth2/token` | ❌ | Exchange code for tokens |
858
+ | GET | `/api/v1/oauth2/userinfo` | βœ… (OAuth token) | Get user info (OIDC-compatible) |
859
+
860
+ ### Health APIs
861
+
862
+ | Method | Endpoint | Description |
863
+ |--------|----------|-------------|
864
+ | GET | `/health` | Simple health check |
865
+ | GET | `/health/ready` | Deep readiness check (DB, Redis, memory) |
866
+
867
+ ---
868
+
869
+ ## 9. Data Types & Interfaces
870
+
871
+ ### Login Response
872
+
873
+ ```typescript
874
+ interface LoginResponse {
875
+ success: boolean;
876
+ data: {
877
+ user: User;
878
+ access_token: string;
879
+ refresh_token: string;
880
+ expires_in: number; // seconds (3600 = 1 hour)
881
+ is_new_user: boolean;
882
+ // MFA fields (only present if MFA required)
883
+ mfa_required?: boolean;
884
+ mfa_token?: string;
885
+ };
886
+ }
887
+ ```
888
+
889
+ ### User
890
+
891
+ ```typescript
892
+ interface User {
893
+ id: string; // UUID
894
+ phone?: string | null; // e.g. "+8613800138000"
895
+ email?: string | null;
896
+ nickname?: string | null;
897
+ avatar_url?: string | null;
898
+ }
899
+ ```
900
+
901
+ ### Token Payload (JWT Claims)
902
+
903
+ ```typescript
904
+ interface TokenPayload {
905
+ sub: string; // User ID
906
+ iss?: string; // Issuer ("https://sso.55387.xyz")
907
+ aud?: string | string[]; // Audience (your client_id)
908
+ exp: number; // Expiration (Unix timestamp)
909
+ iat: number; // Issued at (Unix timestamp)
910
+ scope?: string; // Space-separated scopes
911
+ email?: string;
912
+ phone?: string;
913
+ }
914
+ ```
915
+
916
+ ### UserInfo (from Server SDK / OAuth2 userinfo endpoint)
917
+
918
+ ```typescript
919
+ interface UserInfo {
920
+ id: string; // (same as `sub`)
921
+ phone?: string;
922
+ email?: string;
923
+ nickname?: string;
924
+ avatar_url?: string;
925
+ phone_verified?: boolean;
926
+ email_verified?: boolean;
927
+ }
928
+ ```
929
+
930
+ ### OAuth2 Token Response
931
+
932
+ ```typescript
933
+ interface OAuth2TokenResponse {
934
+ access_token: string;
935
+ token_type: string; // "Bearer"
936
+ expires_in: number; // 3600
937
+ refresh_token: string;
938
+ id_token?: string; // Present when scope includes "openid"
939
+ }
940
+ ```
941
+
942
+ ### SSO Callback Result
943
+
944
+ ```typescript
945
+ interface SSOResult {
946
+ access_token: string;
947
+ refresh_token?: string;
948
+ expires_in?: number;
949
+ token_type: string; // "Bearer"
950
+ id_token?: string;
951
+ }
952
+ ```
953
+
954
+ ### Error Response
955
+
956
+ ```typescript
957
+ interface ErrorResponse {
958
+ success: false;
959
+ error: {
960
+ code: string; // e.g. "INVALID_CODE", "TOKEN_EXPIRED"
961
+ message: string; // Human-readable error message
962
+ };
963
+ }
964
+ ```
965
+
966
+ ---
967
+
968
+ ## 10. Error Handling
969
+
970
+ ### Frontend SDK Errors
971
+
972
+ ```typescript
973
+ import { UniAuthError, AuthErrorCode } from '@55387.ai/uniauth-client';
974
+
975
+ try {
976
+ await auth.loginWithCode(phone, code);
977
+ } catch (error) {
978
+ if (error instanceof UniAuthError) {
979
+ switch (error.code) {
980
+ case AuthErrorCode.MFA_REQUIRED:
981
+ // Handle MFA flow
982
+ break;
983
+ case AuthErrorCode.VERIFY_FAILED:
984
+ // Wrong verification code
985
+ break;
986
+ default:
987
+ console.error(error.message);
988
+ }
989
+ }
990
+ }
991
+ ```
992
+
993
+ ### Backend SDK Errors
994
+
995
+ ```typescript
996
+ import { ServerAuthError, ServerErrorCode } from '@55387.ai/uniauth-server';
997
+
998
+ try {
999
+ await uniauth.verifyToken(token);
1000
+ } catch (error) {
1001
+ if (error instanceof ServerAuthError) {
1002
+ switch (error.code) {
1003
+ case ServerErrorCode.INVALID_TOKEN:
1004
+ // Token format invalid
1005
+ break;
1006
+ case ServerErrorCode.TOKEN_EXPIRED:
1007
+ // Token expired β€” frontend should use refresh_token
1008
+ break;
1009
+ case ServerErrorCode.UNAUTHORIZED:
1010
+ // User not authenticated
1011
+ break;
1012
+ }
1013
+ }
1014
+ }
1015
+ ```
1016
+
1017
+ ### Common HTTP Error Codes
1018
+
1019
+ | Status | Meaning | Action |
1020
+ |--------|---------|--------|
1021
+ | 400 | Bad Request | Check request body / parameters |
1022
+ | 401 | Unauthorized | Token invalid/expired, refresh or re-login |
1023
+ | 404 | Not Found | Check endpoint URL (must include `/api/v1/`) |
1024
+ | 429 | Too Many Requests | Rate limited, wait and retry |
1025
+ | 500 | Server Error | UniAuth server issue, retry later |
1026
+
1027
+ ---
1028
+
1029
+ ## 11. Security Best Practices
1030
+
1031
+ ### Token Strategy
1032
+
1033
+ | Token | Lifetime | Storage Location |
1034
+ |-------|----------|------------------|
1035
+ | Access Token | 1 hour | Memory or localStorage (frontend) |
1036
+ | Refresh Token | 30 days | httpOnly cookie or secure storage |
1037
+ | ID Token | 24 hours | httpOnly cookie (backend SSO flow) |
1038
+ | Authorization Code | 10 minutes | Single-use, never store |
1039
+
1040
+ ### Rules
1041
+
1042
+ 1. **NEVER expose `client_secret` in frontend code** β€” use backend-proxy flow for confidential clients
1043
+ 2. **Always use HTTPS** in production
1044
+ 3. **Use `state` parameter** for CSRF protection in OAuth2 flows
1045
+ 4. **Use PKCE** for all public clients (SPA, mobile apps)
1046
+ 5. **Validate `redirect_uri`** β€” must match exactly what's registered
1047
+ 6. **Validate JWT tokens** β€” check signature, issuer, audience, expiration
1048
+ 7. **Store sensitive tokens in httpOnly cookies** when possible
1049
+ 8. **Implement token rotation** β€” UniAuth automatically rotates refresh tokens
1050
+ 9. **Use rate limiting** β€” UniAuth enforces: SMS 1/min, verify 5/15min
1051
+
1052
+ ---
1053
+
1054
+ ## 12. Framework-Specific Examples
1055
+
1056
+ ### React (with Client SDK)
1057
+
1058
+ ```tsx
1059
+ // contexts/AuthContext.tsx
1060
+ import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react';
1061
+ import { UniAuthClient } from '@55387.ai/uniauth-client';
1062
+
1063
+ interface AuthContextType {
1064
+ user: any | null;
1065
+ isAuthenticated: boolean;
1066
+ isLoading: boolean;
1067
+ login: (phone: string, code: string) => Promise<void>;
1068
+ logout: () => Promise<void>;
1069
+ }
1070
+
1071
+ const AuthContext = createContext<AuthContextType | undefined>(undefined);
1072
+
1073
+ const auth = new UniAuthClient({
1074
+ baseUrl: 'https://sso.55387.xyz',
1075
+ });
1076
+
1077
+ export function AuthProvider({ children }: { children: ReactNode }) {
1078
+ const [user, setUser] = useState<any>(null);
1079
+ const [isLoading, setIsLoading] = useState(true);
1080
+
1081
+ useEffect(() => {
1082
+ // Check existing auth state on mount
1083
+ if (auth.isAuthenticated()) {
1084
+ auth.getCurrentUser().then(setUser).catch(() => setUser(null)).finally(() => setIsLoading(false));
1085
+ } else {
1086
+ setIsLoading(false);
1087
+ }
1088
+
1089
+ // Listen for auth state changes
1090
+ const unsubscribe = auth.onAuthStateChange((u, isAuth) => {
1091
+ setUser(isAuth ? u : null);
1092
+ });
1093
+ return unsubscribe;
1094
+ }, []);
1095
+
1096
+ const login = async (phone: string, code: string) => {
1097
+ const result = await auth.loginWithCode(phone, code);
1098
+ if (result.mfa_required) {
1099
+ throw new Error('MFA_REQUIRED');
1100
+ }
1101
+ setUser(result.data?.user);
1102
+ };
1103
+
1104
+ const logout = async () => {
1105
+ await auth.logout();
1106
+ setUser(null);
1107
+ };
1108
+
1109
+ return (
1110
+ <AuthContext.Provider value={{ user, isAuthenticated: !!user, isLoading, login, logout }}>
1111
+ {children}
1112
+ </AuthContext.Provider>
1113
+ );
1114
+ }
1115
+
1116
+ export const useAuth = () => {
1117
+ const ctx = useContext(AuthContext);
1118
+ if (!ctx) throw new Error('useAuth must be used within AuthProvider');
1119
+ return ctx;
1120
+ };
1121
+ ```
1122
+
1123
+ ### Vue 3 (Composable)
1124
+
1125
+ ```typescript
1126
+ // composables/useAuth.ts
1127
+ import { ref, onMounted } from 'vue';
1128
+ import { UniAuthClient } from '@55387.ai/uniauth-client';
1129
+
1130
+ const auth = new UniAuthClient({ baseUrl: 'https://sso.55387.xyz' });
1131
+ const user = ref<any>(null);
1132
+ const isAuthenticated = ref(false);
1133
+ const isLoading = ref(true);
1134
+
1135
+ export function useAuth() {
1136
+ onMounted(async () => {
1137
+ if (auth.isAuthenticated()) {
1138
+ try {
1139
+ user.value = await auth.getCurrentUser();
1140
+ isAuthenticated.value = true;
1141
+ } catch {
1142
+ user.value = null;
1143
+ isAuthenticated.value = false;
1144
+ }
1145
+ }
1146
+ isLoading.value = false;
1147
+ });
1148
+
1149
+ const login = async (phone: string, code: string) => {
1150
+ const result = await auth.loginWithCode(phone, code);
1151
+ user.value = result.data?.user;
1152
+ isAuthenticated.value = true;
1153
+ };
1154
+
1155
+ const logout = async () => {
1156
+ await auth.logout();
1157
+ user.value = null;
1158
+ isAuthenticated.value = false;
1159
+ };
1160
+
1161
+ return { user, isAuthenticated, isLoading, login, logout, auth };
1162
+ }
1163
+ ```
1164
+
1165
+ ### Express.js (Full Backend)
1166
+
1167
+ ```typescript
1168
+ import express from 'express';
1169
+ import { UniAuthServer } from '@55387.ai/uniauth-server';
1170
+
1171
+ const app = express();
1172
+ const auth = new UniAuthServer({
1173
+ baseUrl: process.env.UNIAUTH_URL || 'https://sso.55387.xyz',
1174
+ clientId: process.env.UNIAUTH_CLIENT_ID!,
1175
+ clientSecret: process.env.UNIAUTH_CLIENT_SECRET!,
1176
+ });
1177
+
1178
+ // Public routes
1179
+ app.get('/health', (req, res) => res.json({ status: 'ok' }));
1180
+
1181
+ // Protected routes
1182
+ app.use('/api/*', auth.middleware());
1183
+
1184
+ app.get('/api/profile', (req, res) => {
1185
+ res.json({ user: req.user });
1186
+ });
1187
+
1188
+ app.get('/api/data', (req, res) => {
1189
+ const userId = req.authPayload?.sub;
1190
+ // Fetch user-specific data...
1191
+ res.json({ userId, data: [] });
1192
+ });
1193
+
1194
+ app.listen(3000, () => console.log('Server running on :3000'));
1195
+ ```
1196
+
1197
+ ---
1198
+
1199
+ ## 13. Troubleshooting
1200
+
1201
+ | Error | Cause | Solution |
1202
+ |-------|-------|----------|
1203
+ | `invalid_client` | Wrong `client_id` | Verify credentials in Developer Console |
1204
+ | `Client authentication failed` | Wrong `client_secret` or using confidential client from frontend | Use backend-proxy flow, or switch to Public Client |
1205
+ | `invalid_grant` | Auth code expired (10 min) or already used | Codes are single-use, request a new one |
1206
+ | `redirect_uri mismatch` | Callback URL doesn't match registered URI | URLs must match exactly (protocol + domain + port + path) |
1207
+ | `PKCE required` | Public client must use PKCE | Use `auth.loginWithSSO({ usePKCE: true })` |
1208
+ | 404 on OAuth2 endpoints | Wrong endpoint path | Use `/api/v1/oauth2/authorize`, not `/oauth2/authorize` |
1209
+ | Token expired (401) | Access token expired | Use refresh token or `auth.getAccessToken()` (auto-refreshes) |
1210
+ | `ERR_SSL_PROTOCOL_ERROR` | Using HTTPS on localhost | Use `http://localhost:3000` for local development |
1211
+
1212
+ ### Debugging Tips
1213
+
1214
+ 1. Check the OIDC discovery document: `https://sso.55387.xyz/.well-known/openid-configuration`
1215
+ 2. Decode JWTs at [jwt.io](https://jwt.io) to inspect claims
1216
+ 3. Test API calls with cURL before implementing in code
1217
+ 4. Enable `onAuthError` callback in the Client SDK for early error detection
1218
+ 5. Check UniAuth API docs at `https://sso.55387.xyz/docs` (Swagger UI)
1219
+
1220
+ ---
1221
+
1222
+ ## 14. FAQ
1223
+
1224
+ **Q: Can I verify tokens locally without calling UniAuth?**
1225
+ A: Yes. Configure `jwtPublicKey` in the Server SDK, or fetch the JWKS from `/.well-known/jwks.json`. Default behavior is remote verification with 1-minute cache.
1226
+
1227
+ **Q: Who handles token refresh?**
1228
+ A: The frontend Client SDK handles auto-refresh via `getAccessToken()`. Backend only needs to verify the access token.
1229
+
1230
+ **Q: How long is the token cache?**
1231
+ A: Server SDK caches verification results for 1 minute. Call `clearCache()` to clear.
1232
+
1233
+ **Q: Can I use UniAuth without the SDK?**
1234
+ A: Yes, see Method D (Direct API) or Method C (OIDC standard library).
1235
+
1236
+ **Q: What scopes are available?**
1237
+ A: `openid`, `profile`, `email`, `phone`. Use `openid profile email phone` for full access.
1238
+
1239
+ **Q: Is M2M (machine-to-machine) authentication supported?**
1240
+ A: Yes, use Client Credentials flow:
1241
+ ```typescript
1242
+ const result = await client.loginWithClientCredentials('scope1 scope2');
1243
+ ```
1244
+
1245
+ ---
1246
+
1247
+ ## Quick Reference Card
1248
+
1249
+ ```
1250
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
1251
+ β”‚ UniAuth Quick Reference β”‚
1252
+ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
1253
+ β”‚ Service URL: https://sso.55387.xyz β”‚
1254
+ β”‚ Swagger: https://sso.55387.xyz/docs β”‚
1255
+ β”‚ OIDC Discovery: https://sso.55387.xyz/.well-known/openid-configuration β”‚
1256
+ β”‚ JWKS: https://sso.55387.xyz/.well-known/jwks.json β”‚
1257
+ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
1258
+ β”‚ Frontend SDK: npm install @55387.ai/uniauth-client β”‚
1259
+ β”‚ Backend SDK: npm install @55387.ai/uniauth-server β”‚
1260
+ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
1261
+ β”‚ Access Token: 1h β”‚ Refresh Token: 30d β”‚ ID Token: 24hβ”‚
1262
+ β”‚ Auth Code: 10min β”‚ SMS Rate: 1/min β”‚ PKCE: S256 β”‚
1263
+ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
1264
+ β”‚ Key Endpoints: β”‚
1265
+ β”‚ POST /api/v1/auth/phone/send-code β€” Send SMS code β”‚
1266
+ β”‚ POST /api/v1/auth/phone/verify β€” SMS login β”‚
1267
+ β”‚ POST /api/v1/auth/email/login β€” Email+password β”‚
1268
+ β”‚ POST /api/v1/auth/refresh β€” Refresh token β”‚
1269
+ β”‚ GET /api/v1/user/me β€” Get current user β”‚
1270
+ β”‚ POST /api/v1/oauth2/token β€” OAuth2 token exchangeβ”‚
1271
+ β”‚ GET /api/v1/oauth2/userinfo β€” OIDC userinfo β”‚
1272
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
1273
+ ```