@mspkapps/auth-client 0.1.24 → 0.1.26

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/README.md CHANGED
@@ -1,19 +1,16 @@
1
- # MSPK™ Auth Client (`@mspkapps/auth-client`)
1
+ # AuthClient NPM Package Documentation (Backend-Only Usage)
2
2
 
3
- Simple backend SDK for the MSPK™ Auth Platform.
4
- Use it from your server code to handle login, register, Google auth, password flows, and profile operations with a few lines.
5
-
6
- - ✅ Email/password login & register
7
- - ✅ Google OAuth login
8
- - ✅ Password reset & change flows
9
- - ✅ Email verification / resend flows
10
- - ✅ Account delete
11
- - ✅ Profile read & update
12
- - ✅ Simple singleton API: `authclient.init(...)` then `authclient.login(...)`
3
+ > All usage of `@mspkapps/auth-client` and all API keys/secrets **must live in your backend only**.
4
+ > Frontend apps (React / React Native) should **never** import this package or see the keys.
5
+ > They only call your backend, and your backend calls the AuthClient.
13
6
 
14
7
  ---
15
8
 
16
- ## Installation
9
+ ## 1. Backend Setup (Node / Express)
10
+
11
+ ### 1.1 Install in Backend Only
12
+
13
+ In your backend project (e.g. `Backend/`):
17
14
 
18
15
  ```bash
19
16
  npm install @mspkapps/auth-client
@@ -21,356 +18,666 @@ npm install @mspkapps/auth-client
21
18
  yarn add @mspkapps/auth-client
22
19
  ```
23
20
 
24
- ---
21
+ Do **not** install this package in your frontend projects.
25
22
 
26
- ## Backend Usage (Recommended)
23
+ ### 1.2 Environment Variables (Backend)
27
24
 
28
- Most apps should only use this package on the **backend** (Node/Express, NestJS, etc.).
25
+ Create `.env` in your backend root:
29
26
 
30
- ### 1. Initialize once at startup
27
+ ```env
28
+ MSPK_AUTH_API_KEY=your_api_key_here
29
+ MSPK_AUTH_API_SECRET=your_api_secret_here
30
+ GOOGLE_CLIENT_ID=your_google_client_id_here
31
+ ```
31
32
 
32
- ```js
33
- // authClient.js
33
+ ### 1.3 Initialize AuthClient Singleton
34
+
35
+ Create `src/auth/authClient.js` in your backend:
36
+
37
+ ```javascript
34
38
  import authclient from '@mspkapps/auth-client';
35
39
 
40
+ // Initialize once at backend startup
36
41
  authclient.init({
37
42
  apiKey: process.env.MSPK_AUTH_API_KEY,
38
43
  apiSecret: process.env.MSPK_AUTH_API_SECRET,
39
- googleClientId: process.env.GOOGLE_CLIENT_ID, // optional, for Google OAuth
40
- // baseUrl: 'https://cpanel.backend.mspkapps.in/api/v1', // optional override
44
+ googleClientId: process.env.GOOGLE_CLIENT_ID
45
+ // storage: omit on backend (no localStorage)
46
+ // fetch: use global fetch (Node 18+) or pass custom
41
47
  });
42
48
 
43
49
  export default authclient;
44
50
  ```
45
51
 
46
- ### 2. Use in your routes/handlers
52
+ ### 1.4 Express Routes That Proxy to AuthClient
47
53
 
48
- ```js
49
- // exampleRoute.js
50
- import authclient from './authClient.js';
54
+ Example `src/routes/authRoutes.js`:
51
55
 
52
- // Login (email + password)
53
- export async function loginHandler(req, res) {
54
- const { email, password } = req.body;
56
+ ```javascript
57
+ import express from 'express';
58
+ import authclient from '../auth/authClient.js';
59
+ import { AuthError } from '@mspkapps/auth-client';
55
60
 
56
- try {
57
- const result = await authclient.login({ email, password });
58
- // result.data.user, result.data.user_token, etc.
59
- res.json(result);
60
- } catch (err) {
61
- res.status(err.status || 400).json({
61
+ const router = express.Router();
62
+
63
+ // Helper to normalize errors for the frontend
64
+ function handleError(res, err, fallback = 'Request failed') {
65
+ if (err instanceof AuthError) {
66
+ return res.status(err.status || 400).json({
62
67
  success: false,
63
- message: err.message || 'Login failed',
64
- code: err.code || 'LOGIN_FAILED',
68
+ message: err.message || fallback,
69
+ code: err.code || 'REQUEST_FAILED',
70
+ data: err.response?.data ?? null,
65
71
  });
66
72
  }
73
+
74
+ console.error('Unexpected auth error:', err);
75
+ return res.status(500).json({
76
+ success: false,
77
+ message: fallback,
78
+ code: 'INTERNAL_ERROR',
79
+ });
67
80
  }
68
- ```
69
81
 
70
- ```js
71
- // Register
72
- export async function registerHandler(req, res) {
73
- const { email, username, password, name, ...extra } = req.body;
82
+ // POST /api/auth/register
83
+ router.post('/register', async (req, res) => {
84
+ try {
85
+ const { email, username, password, name, extra } = req.body;
86
+ const resp = await authclient.register({ email, username, password, name, extra });
87
+ return res.json(resp);
88
+ } catch (err) {
89
+ return handleError(res, err, 'Registration failed');
90
+ }
91
+ });
74
92
 
93
+ // POST /api/auth/login
94
+ router.post('/login', async (req, res) => {
75
95
  try {
76
- const result = await authclient.register({
77
- email,
78
- username,
79
- password,
80
- name,
81
- ...extra, // custom fields, if configured in MSPK
82
- });
83
- res.json(result);
96
+ const { email, username, password } = req.body;
97
+ const resp = await authclient.login({ email, username, password });
98
+ return res.json(resp);
84
99
  } catch (err) {
85
- res.status(err.status || 400).json({
86
- success: false,
87
- message: err.message || 'Registration failed',
88
- code: err.code || 'REGISTER_FAILED',
89
- });
100
+ return handleError(res, err, 'Login failed');
90
101
  }
91
- }
92
- ```
102
+ });
93
103
 
94
- ---
104
+ // POST /api/auth/google
105
+ router.post('/google', async (req, res) => {
106
+ try {
107
+ const { id_token } = req.body; // Frontend sends Google ID token
108
+ const resp = await authclient.googleAuth({ id_token });
109
+ return res.json(resp);
110
+ } catch (err) {
111
+ return handleError(res, err, 'Google auth failed');
112
+ }
113
+ });
95
114
 
96
- ## API Reference – What Data Each Call Needs
115
+ // POST /api/auth/request-password-reset
116
+ router.post('/request-password-reset', async (req, res) => {
117
+ try {
118
+ const { email } = req.body;
119
+ const resp = await authclient.client.requestPasswordReset({ email });
120
+ return res.json(resp);
121
+ } catch (err) {
122
+ return handleError(res, err, 'Password reset request failed');
123
+ }
124
+ });
97
125
 
98
- All methods below assume you already called:
126
+ // POST /api/auth/request-change-password-link
127
+ router.post('/request-change-password-link', async (req, res) => {
128
+ try {
129
+ const { email } = req.body;
130
+ const resp = await authclient.client.requestChangePasswordLink({ email });
131
+ return res.json(resp);
132
+ } catch (err) {
133
+ return handleError(res, err, 'Change password link request failed');
134
+ }
135
+ });
99
136
 
100
- ```js
101
- import authclient from '@mspkapps/auth-client';
137
+ // POST /api/auth/resend-verification
138
+ router.post('/resend-verification', async (req, res) => {
139
+ try {
140
+ const { email, purpose } = req.body;
141
+ const resp = await authclient.client.resendVerificationEmail({ email, purpose });
142
+ return res.json(resp);
143
+ } catch (err) {
144
+ return handleError(res, err, 'Resend verification failed');
145
+ }
146
+ });
102
147
 
103
- authclient.init({
104
- apiKey: 'YOUR_API_KEY',
105
- apiSecret: 'YOUR_API_SECRET',
106
- googleClientId: 'YOUR_GOOGLE_CLIENT_ID', // optional
148
+ // POST /api/auth/delete-account
149
+ router.post('/delete-account', async (req, res) => {
150
+ try {
151
+ const { email, password } = req.body;
152
+ const resp = await authclient.client.deleteAccount({ email, password });
153
+ return res.json(resp);
154
+ } catch (err) {
155
+ return handleError(res, err, 'Delete account failed');
156
+ }
157
+ });
158
+
159
+ // POST /api/auth/verify-token
160
+ router.post('/verify-token', async (req, res) => {
161
+ try {
162
+ const { accessToken } = req.body;
163
+ const data = await authclient.verifyToken(accessToken);
164
+ return res.json({ success: true, data });
165
+ } catch (err) {
166
+ return handleError(res, err, 'Token verification failed');
167
+ }
107
168
  });
169
+
170
+ export default router;
108
171
  ```
109
172
 
110
- ### Auth & Users
173
+ ### 1.5 Protected User Routes (Backend)
111
174
 
112
- #### `authclient.login({ email, password })` or `authclient.login({ username, password })`
175
+ You typically store the `user_token` provided by AuthClient in your own session/JWT.
176
+ Example `src/middleware/requireAuth.js` that verifies your own access token using `authclient.verifyToken`:
113
177
 
114
- - Required:
115
- - `email` **or** `username` (string)
116
- - `password` (string)
117
- - Returns:
118
- - User data and user token; token is stored internally via `setToken`.
178
+ ```javascript
179
+ import authclient from '../auth/authClient.js';
119
180
 
120
- #### `authclient.register({ email, username?, password, name?, ...extraFields })`
181
+ export async function requireAuth(req, res, next) {
182
+ try {
183
+ const authHeader = req.headers.authorization || '';
184
+ const token = authHeader.replace(/^Bearer\s+/i, '');
121
185
 
122
- - Required:
123
- - `email` (string)
124
- - `password` (string)
125
- - Optional:
126
- - `username` (string)
127
- - `name` (string)
128
- - Any extra profile fields you enabled in MSPK (e.g. `company`, `country`).
129
- - Returns:
130
- - User data and user token; token is stored internally.
186
+ if (!token) {
187
+ return res.status(401).json({ success: false, message: 'Missing access token' });
188
+ }
131
189
 
132
- #### `authclient.googleAuth({ id_token })`
133
- #### `authclient.googleAuth({ access_token })`
190
+ const data = await authclient.verifyToken(token);
191
+ req.user = data; // attach decoded user data to request
192
+ return next();
193
+ } catch (err) {
194
+ console.error('Auth middleware error:', err);
195
+ return res.status(401).json({ success: false, message: 'Invalid or expired token' });
196
+ }
197
+ }
198
+ ```
134
199
 
135
- - Required:
136
- - Either:
137
- - `id_token` (string), or
138
- - `access_token` (string)
139
- - The `googleClientId` is taken from `authclient.init(...)` and sent automatically.
140
- - Returns:
141
- - User data, token, and possibly a flag like `is_new_user`.
200
+ Example profile routes in `src/routes/userRoutes.js`:
142
201
 
143
- ---
202
+ ```javascript
203
+ import express from 'express';
204
+ import authclient from '../auth/authClient.js';
205
+ import { requireAuth } from '../middleware/requireAuth.js';
144
206
 
145
- ### Password Flows
207
+ const router = express.Router();
146
208
 
147
- #### `authclient.client.requestPasswordReset({ email })`
209
+ // GET /api/user/profile
210
+ router.get('/profile', requireAuth, async (req, res) => {
211
+ try {
212
+ const resp = await authclient.getProfile();
213
+ return res.json(resp);
214
+ } catch (err) {
215
+ console.error('Get profile failed:', err);
216
+ return res.status(500).json({ success: false, message: 'Get profile failed' });
217
+ }
218
+ });
148
219
 
149
- - Required:
150
- - `email` (string)
151
- - Use when: user clicks “Forgot password?”
220
+ // PATCH /api/user/profile
221
+ router.patch('/profile', requireAuth, async (req, res) => {
222
+ try {
223
+ const updates = req.body;
224
+ const resp = await authclient.updateProfile(updates);
225
+ return res.json(resp);
226
+ } catch (err) {
227
+ console.error('Update profile failed:', err);
228
+ return res.status(500).json({ success: false, message: 'Update profile failed' });
229
+ }
230
+ });
152
231
 
153
- #### `authclient.client.requestChangePasswordLink({ email })`
232
+ export default router;
233
+ ```
154
234
 
155
- - Required:
156
- - `email` (string)
157
- - Use when: logged-in user requests a “change password” email from settings/profile.
235
+ ### 1.6 Wire Up Routes in Backend App
158
236
 
159
- #### `authclient.client.resendVerificationEmail({ email, purpose? })`
237
+ In your backend `src/app.js`:
160
238
 
161
- - Required:
162
- - `email` (string)
163
- - Optional:
164
- - `purpose` (string). Valid values:
165
- - `New Account`
166
- - `Password change`
167
- - `Profile Edit`
168
- - `Forget Password`
169
- - `Delete Account`
170
- - `Set Password - Google User`
171
- - If `purpose` is missing/invalid, the backend will treat it as `New Account`.
239
+ ```javascript
240
+ import express from 'express';
241
+ import cors from 'cors';
242
+ import authRoutes from './routes/authRoutes.js';
243
+ import userRoutes from './routes/userRoutes.js';
172
244
 
173
- #### `authclient.client.sendGoogleUserSetPasswordEmail({ email })`
245
+ const app = express();
174
246
 
175
- - Required:
176
- - `email` (string) – user who signed up via Google and wants a password.
177
- - Use when: a Google-only user wants to set a traditional password.
247
+ app.use(cors({ origin: ['http://localhost:5173', 'http://localhost:3000'], credentials: true }));
248
+ app.use(express.json());
249
+
250
+ app.use('/api/auth', authRoutes);
251
+ app.use('/api/user', userRoutes);
252
+
253
+ export default app;
254
+ ```
178
255
 
179
256
  ---
180
257
 
181
- ### Account Management
258
+ ## 2. React (Vite) Frontend – Call Your Backend
182
259
 
183
- #### `authclient.client.deleteAccount({ email, password })`
260
+ Your React app **does not** import `@mspkapps/auth-client` and knows nothing about API keys.
261
+ It only calls your backend routes like `/api/auth/login`, `/api/auth/register`, etc.
184
262
 
185
- - Required:
186
- - `email` (string)
187
- - `password` (string) – current password (depending on your server rules).
188
- - Use when: user confirms account deletion.
263
+ ### 2.1 API Service
189
264
 
190
- ---
265
+ Create `src/services/authApi.js` in your React app:
191
266
 
192
- ### Profile
267
+ ```javascript
268
+ const API_BASE_URL = import.meta.env.VITE_BACKEND_URL || 'http://localhost:4000';
193
269
 
194
- #### `authclient.getProfile()`
270
+ export async function apiLogin({ email, password, username }) {
271
+ const resp = await fetch(`${API_BASE_URL}/api/auth/login`, {
272
+ method: 'POST',
273
+ headers: { 'Content-Type': 'application/json' },
274
+ body: JSON.stringify({ email, password, username }),
275
+ });
195
276
 
196
- - No parameters.
197
- - Uses the internally stored user token.
198
- - Returns current user profile.
277
+ const json = await resp.json();
278
+ if (!resp.ok || json?.success === false) {
279
+ throw new Error(json?.message || 'Login failed');
280
+ }
281
+ return json;
282
+ }
199
283
 
200
- #### `authclient.client.getEditableProfileFields()`
284
+ export async function apiRegister(payload) {
285
+ const resp = await fetch(`${API_BASE_URL}/api/auth/register`, {
286
+ method: 'POST',
287
+ headers: { 'Content-Type': 'application/json' },
288
+ body: JSON.stringify(payload),
289
+ });
290
+ const json = await resp.json();
291
+ if (!resp.ok || json?.success === false) {
292
+ throw new Error(json?.message || 'Registration failed');
293
+ }
294
+ return json;
295
+ }
201
296
 
202
- - No parameters.
203
- - Returns metadata about which fields this user is allowed to edit based on your MSPK configuration.
297
+ export async function apiGoogleLogin(idToken) {
298
+ const resp = await fetch(`${API_BASE_URL}/api/auth/google`, {
299
+ method: 'POST',
300
+ headers: { 'Content-Type': 'application/json' },
301
+ body: JSON.stringify({ id_token: idToken }),
302
+ });
303
+ const json = await resp.json();
304
+ if (!resp.ok || json?.success === false) {
305
+ throw new Error(json?.message || 'Google login failed');
306
+ }
307
+ return json;
308
+ }
204
309
 
205
- #### `authclient.updateProfile(updates)`
310
+ export async function apiGetProfile(accessToken) {
311
+ const resp = await fetch(`${API_BASE_URL}/api/user/profile`, {
312
+ method: 'GET',
313
+ headers: {
314
+ 'Content-Type': 'application/json',
315
+ Authorization: `Bearer ${accessToken}`,
316
+ },
317
+ });
318
+ const json = await resp.json();
319
+ if (!resp.ok || json?.success === false) {
320
+ throw new Error(json?.message || 'Get profile failed');
321
+ }
322
+ return json;
323
+ }
324
+ ```
206
325
 
207
- ```js
208
- const res = await authclient.updateProfile({
209
- name: 'New Name', // optional
210
- username: 'new_username', // optional
211
- email: 'new@example.com', // optional; may trigger verification
212
- extra: { // optional; your custom fields
213
- company: 'New Co',
214
- country: 'US',
215
- },
216
- });
326
+ ### 2.2 Simple Auth Context (Frontend-Only State)
327
+
328
+ Create `src/context/AuthContext.jsx`:
329
+
330
+ ```jsx
331
+ import { createContext, useContext, useState, useEffect } from 'react';
332
+ import { apiLogin, apiRegister, apiGoogleLogin, apiGetProfile } from '../services/authApi';
333
+
334
+ const AuthContext = createContext(null);
335
+
336
+ export const AuthProvider = ({ children }) => {
337
+ const [user, setUser] = useState(null);
338
+ const [accessToken, setAccessToken] = useState(null);
339
+ const [loading, setLoading] = useState(true);
340
+
341
+ useEffect(() => {
342
+ const token = window.localStorage.getItem('access_token');
343
+ if (token) {
344
+ setAccessToken(token);
345
+ refreshProfile(token).finally(() => setLoading(false));
346
+ } else {
347
+ setLoading(false);
348
+ }
349
+ }, []);
350
+
351
+ const refreshProfile = async (token) => {
352
+ try {
353
+ const resp = await apiGetProfile(token);
354
+ setUser(resp.data);
355
+ } catch {
356
+ setUser(null);
357
+ setAccessToken(null);
358
+ window.localStorage.removeItem('access_token');
359
+ }
360
+ };
361
+
362
+ const login = async (credentials) => {
363
+ const resp = await apiLogin(credentials);
364
+ const token = resp?.data?.access_token || resp?.data?.user_token;
365
+ if (token) {
366
+ setAccessToken(token);
367
+ window.localStorage.setItem('access_token', token);
368
+ await refreshProfile(token);
369
+ }
370
+ return resp;
371
+ };
372
+
373
+ const register = async (payload) => {
374
+ const resp = await apiRegister(payload);
375
+ const token = resp?.data?.access_token || resp?.data?.user_token;
376
+ if (token) {
377
+ setAccessToken(token);
378
+ window.localStorage.setItem('access_token', token);
379
+ await refreshProfile(token);
380
+ }
381
+ return resp;
382
+ };
383
+
384
+ const googleLogin = async (idToken) => {
385
+ const resp = await apiGoogleLogin(idToken);
386
+ const token = resp?.data?.access_token || resp?.data?.user_token;
387
+ if (token) {
388
+ setAccessToken(token);
389
+ window.localStorage.setItem('access_token', token);
390
+ await refreshProfile(token);
391
+ }
392
+ return resp;
393
+ };
394
+
395
+ const logout = () => {
396
+ setUser(null);
397
+ setAccessToken(null);
398
+ window.localStorage.removeItem('access_token');
399
+ };
400
+
401
+ return (
402
+ <AuthContext.Provider
403
+ value={{ user, accessToken, loading, login, register, googleLogin, logout, isAuthenticated: !!user }}
404
+ >
405
+ {children}
406
+ </AuthContext.Provider>
407
+ );
408
+ };
409
+
410
+ export const useAuth = () => {
411
+ const ctx = useContext(AuthContext);
412
+ if (!ctx) throw new Error('useAuth must be used within AuthProvider');
413
+ return ctx;
414
+ };
217
415
  ```
218
416
 
219
- - All keys are **optional**; send only what you want to change.
220
- - Allowed fields depend on:
221
- - Core field permissions (name/username/email).
222
- - Extra fields you defined for the app.
417
+ ### 2.3 Example Login Page (React Vite)
418
+
419
+ ```jsx
420
+ import { useState } from 'react';
421
+ import { useAuth } from '../context/AuthContext';
422
+
423
+ function LoginPage() {
424
+ const { login } = useAuth();
425
+ const [email, setEmail] = useState('');
426
+ const [password, setPassword] = useState('');
427
+ const [error, setError] = useState('');
428
+
429
+ const handleSubmit = async (e) => {
430
+ e.preventDefault();
431
+ setError('');
432
+ try {
433
+ await login({ email, password });
434
+ // navigate to dashboard
435
+ } catch (err) {
436
+ setError(err.message);
437
+ }
438
+ };
439
+
440
+ return (
441
+ <form onSubmit={handleSubmit}>
442
+ <input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" />
443
+ <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password" />
444
+ <button type="submit">Login</button>
445
+ {error && <div style={{ color: 'red' }}>{error}</div>}
446
+ </form>
447
+ );
448
+ }
223
449
 
224
- ---
450
+ export default LoginPage;
451
+ ```
225
452
 
226
- ### Generic Authenticated Calls
453
+ ### 2.4 Google Login (React Vite)
227
454
 
228
- #### `authclient.authed(path, { method = 'GET', body, headers } = {})`
455
+ Frontend just gets the Google `credential` and posts it to your backend:
229
456
 
230
- ```js
231
- const res = await authclient.authed('user/profile', {
232
- method: 'PATCH',
233
- body: { name: 'Another Name' },
234
- headers: {
235
- 'X-Custom-Header': '123',
236
- },
237
- });
457
+ ```bash
458
+ npm install @react-oauth/google
238
459
  ```
239
460
 
240
- - Required:
241
- - `path` (string) relative path (e.g. `'user/profile'`), not full URL.
242
- - Optional:
243
- - `method` (string, default `'GET'`)
244
- - `body` (object) – will be `JSON.stringify`’d
245
- - `headers` (object) – extra headers merged into auth headers.
246
- - Uses the same API key/secret/Google client and user token as other methods.
247
-
248
- ---
461
+ ```jsx
462
+ import { GoogleOAuthProvider, GoogleLogin } from '@react-oauth/google';
463
+ import { useAuth } from '../context/AuthContext';
464
+
465
+ // In your root
466
+ <GoogleOAuthProvider clientId={import.meta.env.VITE_GOOGLE_CLIENT_ID}>
467
+ <AuthProvider>
468
+ <App />
469
+ </AuthProvider>
470
+ </GoogleOAuthProvider>;
471
+
472
+ // In login page
473
+ function LoginPage() {
474
+ const { googleLogin } = useAuth();
475
+
476
+ return (
477
+ <GoogleLogin
478
+ onSuccess={async (credentialResponse) => {
479
+ try {
480
+ await googleLogin(credentialResponse.credential);
481
+ } catch (err) {
482
+ console.error('Google login failed', err);
483
+ }
484
+ }}
485
+ onError={() => console.log('Google login error')}
486
+ />
487
+ );
488
+ }
489
+ ```
249
490
 
250
- ### Token Helpers
491
+ The Google ID token is sent to `/api/auth/google` on your backend, which then calls `authclient.googleAuth`.
251
492
 
252
- #### `authclient.setToken(token)`
493
+ ---
253
494
 
254
- - Required:
255
- - `token` (string or `null`).
256
- - Usually not needed, because:
257
- - `login`, `register`, and `googleAuth` will set the token automatically.
495
+ ## 3. React Native CLI – Call Your Backend
258
496
 
259
- #### `authclient.logout()`
497
+ React Native app also **never** imports `@mspkapps/auth-client`. It talks only to your backend.
260
498
 
261
- - No parameters.
262
- - Clears the stored token in memory (and storage, if configured).
499
+ ### 3.1 API Service (React Native)
263
500
 
264
- ---
501
+ Create `src/services/authApi.js`:
265
502
 
266
- ## Example Express Setup
503
+ ```javascript
504
+ const API_BASE_URL = process.env.BACKEND_URL || 'http://10.0.2.2:4000'; // Android emulator example
267
505
 
268
- ```js
269
- // authClient.js
270
- import authclient from '@mspkapps/auth-client';
506
+ async function request(path, { method = 'GET', body, token } = {}) {
507
+ const headers = { 'Content-Type': 'application/json' };
508
+ if (token) headers.Authorization = `Bearer ${token}`;
271
509
 
272
- authclient.init({
273
- apiKey: process.env.MSPK_AUTH_API_KEY,
274
- apiSecret: process.env.MSPK_AUTH_API_SECRET,
275
- googleClientId: process.env.GOOGLE_CLIENT_ID,
276
- });
510
+ const resp = await fetch(`${API_BASE_URL}${path}`, {
511
+ method,
512
+ headers,
513
+ body: body ? JSON.stringify(body) : undefined,
514
+ });
515
+ const json = await resp.json();
516
+ if (!resp.ok || json?.success === false) {
517
+ throw new Error(json?.message || 'Request failed');
518
+ }
519
+ return json;
520
+ }
277
521
 
278
- export default authclient;
522
+ export const apiLogin = (payload) => request('/api/auth/login', { method: 'POST', body: payload });
523
+ export const apiRegister = (payload) => request('/api/auth/register', { method: 'POST', body: payload });
524
+ export const apiGoogleLogin = (idToken) =>
525
+ request('/api/auth/google', { method: 'POST', body: { id_token: idToken } });
526
+ export const apiGetProfile = (token) => request('/api/user/profile', { method: 'GET', token });
279
527
  ```
280
528
 
281
- ```js
282
- // routes/auth.js
283
- import express from 'express';
284
- import authclient from '../authClient.js';
285
-
286
- const router = express.Router();
529
+ ### 3.2 Auth Context (React Native)
530
+
531
+ ```javascript
532
+ import React, { createContext, useContext, useEffect, useState } from 'react';
533
+ import AsyncStorage from '@react-native-async-storage/async-storage';
534
+ import { apiLogin, apiRegister, apiGoogleLogin, apiGetProfile } from '../services/authApi';
535
+
536
+ const AuthContext = createContext(null);
537
+
538
+ export const AuthProvider = ({ children }) => {
539
+ const [user, setUser] = useState(null);
540
+ const [accessToken, setAccessToken] = useState(null);
541
+ const [loading, setLoading] = useState(true);
542
+
543
+ useEffect(() => {
544
+ (async () => {
545
+ const token = await AsyncStorage.getItem('access_token');
546
+ if (token) {
547
+ setAccessToken(token);
548
+ await refreshProfile(token);
549
+ }
550
+ setLoading(false);
551
+ })();
552
+ }, []);
553
+
554
+ const refreshProfile = async (token) => {
555
+ try {
556
+ const resp = await apiGetProfile(token);
557
+ setUser(resp.data);
558
+ } catch {
559
+ setUser(null);
560
+ setAccessToken(null);
561
+ await AsyncStorage.removeItem('access_token');
562
+ }
563
+ };
564
+
565
+ const login = async (payload) => {
566
+ const resp = await apiLogin(payload);
567
+ const token = resp?.data?.access_token || resp?.data?.user_token;
568
+ if (token) {
569
+ setAccessToken(token);
570
+ await AsyncStorage.setItem('access_token', token);
571
+ await refreshProfile(token);
572
+ }
573
+ return resp;
574
+ };
575
+
576
+ const register = async (payload) => {
577
+ const resp = await apiRegister(payload);
578
+ const token = resp?.data?.access_token || resp?.data?.user_token;
579
+ if (token) {
580
+ setAccessToken(token);
581
+ await AsyncStorage.setItem('access_token', token);
582
+ await refreshProfile(token);
583
+ }
584
+ return resp;
585
+ };
586
+
587
+ const googleLogin = async (idToken) => {
588
+ const resp = await apiGoogleLogin(idToken);
589
+ const token = resp?.data?.access_token || resp?.data?.user_token;
590
+ if (token) {
591
+ setAccessToken(token);
592
+ await AsyncStorage.setItem('access_token', token);
593
+ await refreshProfile(token);
594
+ }
595
+ return resp;
596
+ };
597
+
598
+ const logout = async () => {
599
+ setUser(null);
600
+ setAccessToken(null);
601
+ await AsyncStorage.removeItem('access_token');
602
+ };
603
+
604
+ return (
605
+ <AuthContext.Provider
606
+ value={{ user, accessToken, loading, login, register, googleLogin, logout, isAuthenticated: !!user }}
607
+ >
608
+ {children}
609
+ </AuthContext.Provider>
610
+ );
611
+ };
612
+
613
+ export const useAuth = () => {
614
+ const ctx = useContext(AuthContext);
615
+ if (!ctx) throw new Error('useAuth must be used within AuthProvider');
616
+ return ctx;
617
+ };
618
+ ```
287
619
 
288
- router.post('/login', async (req, res) => {
289
- const { email, password, username } = req.body;
620
+ ### 3.3 Google Sign-In (React Native)
290
621
 
291
- try {
292
- const result = await authclient.login(
293
- email ? { email, password } : { username, password }
294
- );
295
- res.json(result);
296
- } catch (err) {
297
- res.status(err.status || 400).json({
298
- success: false,
299
- message: err.message || 'Login failed',
300
- code: err.code || 'LOGIN_FAILED',
301
- });
302
- }
303
- });
622
+ ```bash
623
+ npm install @react-native-google-signin/google-signin
624
+ ```
304
625
 
305
- router.post('/register', async (req, res) => {
306
- const { email, username, password, name, ...extra } = req.body;
626
+ ```javascript
627
+ import { GoogleSignin } from '@react-native-google-signin/google-signin';
628
+ import { useAuth } from '../context/AuthContext';
307
629
 
308
- try {
309
- const result = await authclient.register({
310
- email,
311
- username,
312
- password,
313
- name,
314
- ...extra,
315
- });
316
- res.json(result);
317
- } catch (err) {
318
- res.status(err.status || 400).json({
319
- success: false,
320
- message: err.message || 'Registration failed',
321
- code: err.code || 'REGISTER_FAILED',
322
- });
323
- }
630
+ GoogleSignin.configure({
631
+ webClientId: 'YOUR_WEB_CLIENT_ID_FROM_GOOGLE',
324
632
  });
325
633
 
326
- export default router;
634
+ function LoginScreen() {
635
+ const { login, googleLogin } = useAuth();
636
+
637
+ const handleGoogle = async () => {
638
+ try {
639
+ await GoogleSignin.hasPlayServices();
640
+ const userInfo = await GoogleSignin.signIn();
641
+ const idToken = userInfo.idToken;
642
+ await googleLogin(idToken); // sends to backend /api/auth/google
643
+ } catch (err) {
644
+ console.error('Google sign-in failed', err);
645
+ }
646
+ };
647
+
648
+ // ...render buttons, etc.
649
+ }
327
650
  ```
328
651
 
329
652
  ---
330
653
 
331
- ## Error Handling
654
+ ## 4. Key & Security Guidelines
332
655
 
333
- All methods throw an `AuthError` when the request fails.
656
+ - `@mspkapps/auth-client` is **backend-only**.
657
+ - API key, secret, and `googleClientId` live **only in backend env vars**.
658
+ - Frontend talks to backend over HTTPS (`/api/auth/*`, `/api/user/*`).
659
+ - Frontend stores only **user-level access token** (e.g. in `localStorage` / `AsyncStorage`).
660
+ - Never expose API key/secret in web or mobile bundles.
334
661
 
335
- Shape (simplified):
662
+ ---
336
663
 
337
- ```ts
338
- class AuthError extends Error {
339
- status: number; // HTTP status code
340
- code: string; // machine-readable code, e.g. 'EMAIL_NOT_VERIFIED'
341
- data: any; // full JSON response (optional)
342
- }
343
- ```
664
+ ## 5. Troubleshooting
344
665
 
345
- Example pattern:
346
-
347
- ```js
348
- try {
349
- const res = await authclient.login({ email, password });
350
- } catch (err) {
351
- if (err.code === 'EMAIL_NOT_VERIFIED') {
352
- // Ask user to verify email or call resendVerificationEmail
353
- } else if (err.status === 401) {
354
- // Invalid credentials
355
- } else {
356
- console.error(err);
357
- }
358
- }
359
- ```
666
+ ### Frontend gets 4xx/5xx from backend
360
667
 
361
- ---
668
+ - Inspect backend logs; most errors will be `AuthError` thrown by AuthClient.
669
+ - Make sure backend env vars (`MSPK_AUTH_API_KEY`, `MSPK_AUTH_API_SECRET`) are set.
362
670
 
363
- ## Security Notes
671
+ ### Google login succeeds on client but fails on backend
364
672
 
365
- - **Backend-only**: Do not expose `apiSecret` in browser or mobile frontend code.
366
- - Frontend apps should call **your backend**, and your backend uses `@mspkapps/auth-client` to talk to the MSPK Auth Platform.
367
- - Always keep `apiKey`, `apiSecret`, and `googleClientId` in environment variables in production.
673
+ - Ensure `GOOGLE_CLIENT_ID` in backend matches the client ID used on the frontend.
674
+ - Check that the frontend sends `credential` / `id_token` to `/api/auth/google` correctly.
368
675
 
369
676
  ---
370
677
 
371
- ## Links & Support
372
-
373
- - npm: `@mspkapps/auth-client`
374
- - MSPK™ Auth Platform dashboard: (URL you provide in your docs)
678
+ ## 6. Summary
375
679
 
376
- For issues or feature requests, open an issue in your repository or contact MSPK™ Auth Platform support.
680
+ - Install and initialize `@mspkapps/auth-client` **only in your backend**.
681
+ - Implement clean REST endpoints in your backend that call `authclient` methods.
682
+ - React and React Native frontends call those endpoints with plain HTTP (fetch/axios).
683
+ - This keeps API keys safe and maintains a clean separation between frontend and backend.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mspkapps/auth-client",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
4
4
  "description": "Lightweight client for Your Auth Service",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/AuthClient.js CHANGED
@@ -94,8 +94,8 @@ export class AuthClient {
94
94
  return json;
95
95
  }
96
96
 
97
- async googleAuth({ id_token, access_token }) {
98
- if (!id_token && !access_token) {
97
+ async googleAuth({ id_token }) {
98
+ if (!id_token) {
99
99
  throw new AuthError(
100
100
  'Either id_token or access_token is required for Google authentication',
101
101
  400,
@@ -105,7 +105,7 @@ export class AuthClient {
105
105
  }
106
106
 
107
107
  // include googleClientId in body too (helpful if backend needs it)
108
- const body = { id_token, access_token };
108
+ const body = { id_token };
109
109
  if (this.googleClientId) body.google_client_id = this.googleClientId;
110
110
 
111
111
  const resp = await this.fetch(this._buildUrl('auth/google'), {