npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mspkapps/auth-client

v0.1.32

Published

Lightweight client for Your Auth Service

Readme

AuthClient NPM Package Documentation (Backend-Only Usage)

All usage of @mspkapps/auth-client and all API keys/secrets must live in your backend only. Frontend apps (React / React Native) should never import this package or see the keys. They only call your backend, and your backend calls the AuthClient.


1. Backend Setup (Node / Express)

1.1 Install in Backend Only

In your backend project (e.g. Backend/):

npm install @mspkapps/auth-client
# or
yarn add @mspkapps/auth-client

Do not install this package in your frontend projects.

1.2 Environment Variables (Backend)

Create .env in your backend root:

MSPK_AUTH_API_KEY=your_api_key_here
MSPK_AUTH_API_SECRET=your_api_secret_here
GOOGLE_CLIENT_ID=your_google_client_id_here

1.3 Initialize AuthClient Singleton

Create src/auth/authClient.js in your backend:

import authclient from '@mspkapps/auth-client';

// Initialize once at backend startup
authclient.init({
  apiKey: process.env.MSPK_AUTH_API_KEY,
  apiSecret: process.env.MSPK_AUTH_API_SECRET,
  googleClientId: process.env.GOOGLE_CLIENT_ID
  // storage: omit on backend (no localStorage)
  // fetch: use global fetch (Node 18+) or pass custom
});

export default authclient;

1.4 Express Routes That Proxy to AuthClient

Example src/routes/authRoutes.js:

import express from 'express';
import authclient from '../auth/authClient.js';
import { AuthError } from '@mspkapps/auth-client';

const router = express.Router();

// Helper to normalize errors for the frontend
function handleError(res, err, fallback = 'Request failed') {
  if (err instanceof AuthError) {
    return res.status(err.status || 400).json({
      success: false,
      message: err.message || fallback,
      code: err.code || 'REQUEST_FAILED',
      data: err.response?.data ?? null,
    });
  }

  console.error('Unexpected auth error:', err);
  return res.status(500).json({
    success: false,
    message: fallback,
    code: 'INTERNAL_ERROR',
  });
}

// POST /api/auth/register
router.post('/register', async (req, res) => {
  try {
    const { email, username, password, name, extra } = req.body;
    const resp = await authclient.register({ email, username, password, name, extra });
    return res.json(resp);
  } catch (err) {
    return handleError(res, err, 'Registration failed');
  }
});

// POST /api/auth/login
router.post('/login', async (req, res) => {
  try {
    const { email, username, password } = req.body;
    const resp = await authclient.login({ email, username, password });
    return res.json(resp);
  } catch (err) {
    return handleError(res, err, 'Login failed');
  }
});

// POST /api/auth/google
router.post('/google', async (req, res) => {
  try {
    const { id_token } = req.body; // Frontend sends Google ID token
    const resp = await authclient.googleAuth({ id_token });
    return res.json(resp);
  } catch (err) {
    return handleError(res, err, 'Google auth failed');
  }
});

// POST /api/auth/request-password-reset
router.post('/request-password-reset', async (req, res) => {
  try {
    const { email } = req.body;
    const resp = await authclient.client.requestPasswordReset({ email });
    return res.json(resp);
  } catch (err) {
    return handleError(res, err, 'Password reset request failed');
  }
});

// POST /api/auth/request-change-password-link
router.post('/request-change-password-link', async (req, res) => {
  try {
    const { email } = req.body;
    const resp = await authclient.client.requestChangePasswordLink({ email });
    return res.json(resp);
  } catch (err) {
    return handleError(res, err, 'Change password link request failed');
  }
});

// POST /api/auth/resend-verification
router.post('/resend-verification', async (req, res) => {
  try {
    const { email, purpose } = req.body;
    const resp = await authclient.client.resendVerificationEmail({ email, purpose });
    return res.json(resp);
  } catch (err) {
    return handleError(res, err, 'Resend verification failed');
  }
});

// POST /api/auth/delete-account
router.post('/delete-account', async (req, res) => {
  try {
    const { email, password } = req.body;
    const resp = await authclient.client.deleteAccount({ email, password });
    return res.json(resp);
  } catch (err) {
    return handleError(res, err, 'Delete account failed');
  }
});

// POST /api/auth/verify-token
router.post('/verify-token', async (req, res) => {
  try {
    const { accessToken } = req.body;
    const data = await authclient.verifyToken(accessToken);
    return res.json({ success: true, data });
  } catch (err) {
    return handleError(res, err, 'Token verification failed');
  }
});

export default router;

1.5 Protected User Routes (Backend)

You typically store the user_token provided by AuthClient in your own session/JWT. Example src/middleware/requireAuth.js that verifies your own access token using authclient.verifyToken:

import authclient from '../auth/authClient.js';

export async function requireAuth(req, res, next) {
  try {
    const authHeader = req.headers.authorization || '';
    const token = authHeader.replace(/^Bearer\s+/i, '');

    if (!token) {
      return res.status(401).json({ success: false, message: 'Missing access token' });
    }

    const data = await authclient.verifyToken(token);
    req.user = data; // attach decoded user data to request
    return next();
  } catch (err) {
    console.error('Auth middleware error:', err);
    return res.status(401).json({ success: false, message: 'Invalid or expired token' });
  }
}

Example profile routes in src/routes/userRoutes.js:

import express from 'express';
import authclient from '../auth/authClient.js';
import { requireAuth } from '../middleware/requireAuth.js';

const router = express.Router();

// GET /api/user/profile
router.get('/profile', requireAuth, async (req, res) => {
  try {
    const resp = await authclient.getProfile();
    return res.json(resp);
  } catch (err) {
    console.error('Get profile failed:', err);
    return res.status(500).json({ success: false, message: 'Get profile failed' });
  }
});

// PATCH /api/user/profile
router.patch('/profile', requireAuth, async (req, res) => {
  try {
    const updates = req.body;
    const resp = await authclient.updateProfile(updates);
    return res.json(resp);
  } catch (err) {
    console.error('Update profile failed:', err);
    return res.status(500).json({ success: false, message: 'Update profile failed' });
  }
});

export default router;

1.6 Developer Data Routes (Backend)

The AuthClient provides APIs to fetch developer-specific data (groups, apps, users). The developer ID is automatically extracted from the API key & secret, so you don't need to pass it explicitly.

Example src/routes/developerRoutes.js:

import express from 'express';
import authclient from '../auth/authClient.js';
import { AuthError } from '@mspkapps/auth-client';

const router = express.Router();

function handleError(res, err, fallback = 'Request failed') {
  if (err instanceof AuthError) {
    return res.status(err.status || 400).json({
      success: false,
      message: err.message || fallback,
      code: err.code || 'REQUEST_FAILED',
      data: err.response?.data ?? null,
    });
  }
  console.error('Unexpected error:', err);
  return res.status(500).json({
    success: false,
    message: fallback,
    code: 'INTERNAL_ERROR',
  });
}

// GET /api/developer/groups
// Fetch all groups belonging to the authenticated developer
router.get('/groups', async (req, res) => {
  try {
    const resp = await authclient.getDeveloperGroups();
    return res.json(resp);
  } catch (err) {
    return handleError(res, err, 'Failed to fetch groups');
  }
});

// GET /api/developer/apps?group_id=123
// Fetch developer's apps
// - No query params: returns ALL apps (with and without groups)
// - ?group_id=123: returns apps in specific group
// - ?group_id=null: returns only apps NOT in any group
router.get('/apps', async (req, res) => {
  try {
    const { group_id } = req.query;
    
    let groupId;
    if (group_id === 'null' || group_id === '') {
      groupId = null; // Apps without groups
    } else if (group_id !== undefined) {
      groupId = group_id; // Specific group
    } // else undefined = all apps
    
    const resp = await authclient.getDeveloperApps(groupId);
    return res.json(resp);
  } catch (err) {
    return handleError(res, err, 'Failed to fetch apps');
  }
});

// GET /api/developer/users?app_id=123&page=1&limit=50
// Fetch users for a specific app (with pagination)
// Returns all user data EXCEPT password, including extra fields
router.get('/users', async (req, res) => {
  try {
    const { app_id, page = 1, limit = 50 } = req.query;
    
    if (!app_id) {
      return res.status(400).json({
        success: false,
        message: 'app_id query parameter is required'
      });
    }
    
    const resp = await authclient.getAppUsers({
      appId: app_id,
      page: parseInt(page),
      limit: parseInt(limit)
    });
    return res.json(resp);
  } catch (err) {
    return handleError(res, err, 'Failed to fetch users');
  }
});

// GET /api/developer/user/:user_id
// Fetch specific user data by user ID
// Returns complete user data (no password) including extra fields
router.get('/user/:user_id', async (req, res) => {
  try {
    const { user_id } = req.params;
    const resp = await authclient.getUserData(user_id);
    return res.json(resp);
  } catch (err) {
    return handleError(res, err, 'Failed to fetch user data');
  }
});

export default router;

Usage Examples:

// Get all groups
GET /api/developer/groups
Response: { success: true, data: [...groups] }

// Get all apps (both in groups and standalone)
GET /api/developer/apps
Response: { success: true, data: [...apps] }

// Get apps in a specific group
GET /api/developer/apps?group_id=123
Response: { success: true, data: [...apps] }

// Get only apps NOT in any group
GET /api/developer/apps?group_id=null
Response: { success: true, data: [...apps] }

// Get users for an app (paginated)
GET /api/developer/users?app_id=456&page=1&limit=50
Response: {
  success: true,
  data: [...users],
  pagination: {
    currentPage: 1,
    totalPages: 5,
    totalUsers: 234,
    limit: 50
  }
}

// Get specific user
GET /api/developer/user/789
Response: {
  success: true,
  data: {
    id: 789,
    username: "john_doe",
    email: "[email protected]",
    name: "John Doe",
    extra: { country: "USA", age: 30 },
    is_email_verified: true,
    // ... no password field
  }
}

1.7 Wire Up Routes in Backend App

In your backend src/app.js:

import express from 'express';
import cors from 'cors';
import authRoutes from './routes/authRoutes.js';
import userRoutes from './routes/userRoutes.js';
import developerRoutes from './routes/developerRoutes.js'; // NEW

const app = express();

app.use(cors({ origin: ['http://localhost:5173', 'http://localhost:3000'], credentials: true }));
app.use(express.json());

app.use('/api/auth', authRoutes);
app.use('/api/user', userRoutes);
app.use('/api/developer', developerRoutes); // NEW

export default app;

2. React (Vite) Frontend – Call Your Backend

Your React app does not import @mspkapps/auth-client and knows nothing about API keys. It only calls your backend routes like /api/auth/login, /api/auth/register, etc.

2.1 API Service

Create src/services/authApi.js in your React app:

const API_BASE_URL = import.meta.env.VITE_BACKEND_URL || 'http://localhost:4000';

export async function apiLogin({ email, password, username }) {
  const resp = await fetch(`${API_BASE_URL}/api/auth/login`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email, password, username }),
  });

  const json = await resp.json();
  if (!resp.ok || json?.success === false) {
    throw new Error(json?.message || 'Login failed');
  }
  return json;
}

export async function apiRegister(payload) {
  const resp = await fetch(`${API_BASE_URL}/api/auth/register`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  });
  const json = await resp.json();
  if (!resp.ok || json?.success === false) {
    throw new Error(json?.message || 'Registration failed');
  }
  return json;
}

export async function apiGoogleLogin(idToken) {
  const resp = await fetch(`${API_BASE_URL}/api/auth/google`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ id_token: idToken }),
  });
  const json = await resp.json();
  if (!resp.ok || json?.success === false) {
    throw new Error(json?.message || 'Google login failed');
  }
  return json;
}

export async function apiGetProfile(accessToken) {
  const resp = await fetch(`${API_BASE_URL}/api/user/profile`, {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${accessToken}`,
    },
  });
  const json = await resp.json();
  if (!resp.ok || json?.success === false) {
    throw new Error(json?.message || 'Get profile failed');
  }
  return json;
}

// Developer Data APIs (if building a developer dashboard)
export async function apiGetGroups() {
  const resp = await fetch(`${API_BASE_URL}/api/developer/groups`, {
    method: 'GET',
    headers: { 'Content-Type': 'application/json' },
  });
  const json = await resp.json();
  if (!resp.ok || json?.success === false) {
    throw new Error(json?.message || 'Get groups failed');
  }
  return json;
}

export async function apiGetApps(groupId) {
  const url = groupId !== undefined
    ? `${API_BASE_URL}/api/developer/apps?group_id=${groupId}`
    : `${API_BASE_URL}/api/developer/apps`;
  const resp = await fetch(url, {
    method: 'GET',
    headers: { 'Content-Type': 'application/json' },
  });
  const json = await resp.json();
  if (!resp.ok || json?.success === false) {
    throw new Error(json?.message || 'Get apps failed');
  }
  return json;
}

export async function apiGetAppUsers(appId, page = 1, limit = 50) {
  const resp = await fetch(
    `${API_BASE_URL}/api/developer/users?app_id=${appId}&page=${page}&limit=${limit}`,
    {
      method: 'GET',
      headers: { 'Content-Type': 'application/json' },
    }
  );
  const json = await resp.json();
  if (!resp.ok || json?.success === false) {
    throw new Error(json?.message || 'Get users failed');
  }
  return json;
}

export async function apiGetUserData(userId) {
  const resp = await fetch(`${API_BASE_URL}/api/developer/user/${userId}`, {
    method: 'GET',
    headers: { 'Content-Type': 'application/json' },
  });
  const json = await resp.json();
  if (!resp.ok || json?.success === false) {
    throw new Error(json?.message || 'Get user data failed');
  }
  return json;
}

2.2 Simple Auth Context (Frontend-Only State)

Create src/context/AuthContext.jsx:

import { createContext, useContext, useState, useEffect } from 'react';
import { apiLogin, apiRegister, apiGoogleLogin, apiGetProfile } from '../services/authApi';

const AuthContext = createContext(null);

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  const [accessToken, setAccessToken] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const token = window.localStorage.getItem('access_token');
    if (token) {
      setAccessToken(token);
      refreshProfile(token).finally(() => setLoading(false));
    } else {
      setLoading(false);
    }
  }, []);

  const refreshProfile = async (token) => {
    try {
      const resp = await apiGetProfile(token);
      setUser(resp.data);
    } catch {
      setUser(null);
      setAccessToken(null);
      window.localStorage.removeItem('access_token');
    }
  };

  const login = async (credentials) => {
    const resp = await apiLogin(credentials);
    const token = resp?.data?.access_token || resp?.data?.user_token;
    if (token) {
      setAccessToken(token);
      window.localStorage.setItem('access_token', token);
      await refreshProfile(token);
    }
    return resp;
  };

  const register = async (payload) => {
    const resp = await apiRegister(payload);
    const token = resp?.data?.access_token || resp?.data?.user_token;
    if (token) {
      setAccessToken(token);
      window.localStorage.setItem('access_token', token);
      await refreshProfile(token);
    }
    return resp;
  };

  const googleLogin = async (idToken) => {
    const resp = await apiGoogleLogin(idToken);
    const token = resp?.data?.access_token || resp?.data?.user_token;
    if (token) {
      setAccessToken(token);
      window.localStorage.setItem('access_token', token);
      await refreshProfile(token);
    }
    return resp;
  };

  const logout = () => {
    setUser(null);
    setAccessToken(null);
    window.localStorage.removeItem('access_token');
  };

  return (
    <AuthContext.Provider
      value={{ user, accessToken, loading, login, register, googleLogin, logout, isAuthenticated: !!user }}
    >
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error('useAuth must be used within AuthProvider');
  return ctx;
};

2.3 Example Login Page (React Vite)

import { useState } from 'react';
import { useAuth } from '../context/AuthContext';

function LoginPage() {
  const { login } = useAuth();
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    setError('');
    try {
      await login({ email, password });
      // navigate to dashboard
    } catch (err) {
      setError(err.message);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" />
      <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password" />
      <button type="submit">Login</button>
      {error && <div style={{ color: 'red' }}>{error}</div>}
    </form>
  );
}

export default LoginPage;

2.4 Google Login (React Vite)

Frontend just gets the Google credential and posts it to your backend:

npm install @react-oauth/google
import { GoogleOAuthProvider, GoogleLogin } from '@react-oauth/google';
import { useAuth } from '../context/AuthContext';

// In your root
<GoogleOAuthProvider clientId={import.meta.env.VITE_GOOGLE_CLIENT_ID}>
  <AuthProvider>
    <App />
  </AuthProvider>
</GoogleOAuthProvider>;

// In login page
function LoginPage() {
  const { googleLogin } = useAuth();

  return (
    <GoogleLogin
      onSuccess={async (credentialResponse) => {
        try {
          await googleLogin(credentialResponse.credential);
        } catch (err) {
          console.error('Google login failed', err);
        }
      }}
      onError={() => console.log('Google login error')}
    />
  );
}

The Google ID token is sent to /api/auth/google on your backend, which then calls authclient.googleAuth.

2.5 Example Developer Dashboard Page (React Vite)

If you're building a developer dashboard that displays groups, apps, and users:

import { useState, useEffect } from 'react';
import { apiGetGroups, apiGetApps, apiGetAppUsers } from '../services/authApi';

function DeveloperDashboard() {
  const [groups, setGroups] = useState([]);
  const [apps, setApps] = useState([]);
  const [selectedApp, setSelectedApp] = useState(null);
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    loadData();
  }, []);

  const loadData = async () => {
    try {
      const [groupsResp, appsResp] = await Promise.all([
        apiGetGroups(),
        apiGetApps() // Get all apps
      ]);
      setGroups(groupsResp.data);
      setApps(appsResp.data);
    } catch (err) {
      console.error('Failed to load data:', err);
    } finally {
      setLoading(false);
    }
  };

  const loadUsers = async (appId) => {
    try {
      const resp = await apiGetAppUsers(appId, 1, 50);
      setUsers(resp.data);
      setSelectedApp(appId);
    } catch (err) {
      console.error('Failed to load users:', err);
    }
  };

  if (loading) return <div>Loading...</div>;

  return (
    <div>
      <h1>Developer Dashboard</h1>
      
      <section>
        <h2>Groups ({groups.length})</h2>
        <ul>
          {groups.map(group => (
            <li key={group.id}>{group.name}</li>
          ))}
        </ul>
      </section>

      <section>
        <h2>Apps ({apps.length})</h2>
        <ul>
          {apps.map(app => (
            <li key={app.id}>
              {app.app_name} {app.group_name && `(Group: ${app.group_name})`}
              <button onClick={() => loadUsers(app.id)}>View Users</button>
            </li>
          ))}
        </ul>
      </section>

      {selectedApp && (
        <section>
          <h2>Users for App {selectedApp}</h2>
          <ul>
            {users.map(user => (
              <li key={user.id}>
                {user.email} - {user.name}
              </li>
            ))}
          </ul>
        </section>
      )}
    </div>
  );
}

export default DeveloperDashboard;

3. React Native CLI – Call Your Backend

React Native app also never imports @mspkapps/auth-client. It talks only to your backend.

3.1 API Service (React Native)

Create src/services/authApi.js:

const API_BASE_URL = process.env.BACKEND_URL || 'http://10.0.2.2:4000'; // Android emulator example

async function request(path, { method = 'GET', body, token } = {}) {
  const headers = { 'Content-Type': 'application/json' };
  if (token) headers.Authorization = `Bearer ${token}`;

  const resp = await fetch(`${API_BASE_URL}${path}`, {
    method,
    headers,
    body: body ? JSON.stringify(body) : undefined,
  });
  const json = await resp.json();
  if (!resp.ok || json?.success === false) {
    throw new Error(json?.message || 'Request failed');
  }
  return json;
}

export const apiLogin = (payload) => request('/api/auth/login', { method: 'POST', body: payload });
export const apiRegister = (payload) => request('/api/auth/register', { method: 'POST', body: payload });
export const apiGoogleLogin = (idToken) =>
  request('/api/auth/google', { method: 'POST', body: { id_token: idToken } });
export const apiGetProfile = (token) => request('/api/user/profile', { method: 'GET', token });

// Developer Data APIs
export const apiGetGroups = () => request('/api/developer/groups', { method: 'GET' });
export const apiGetApps = (groupId) => {
  const path = groupId !== undefined 
    ? `/api/developer/apps?group_id=${groupId}` 
    : '/api/developer/apps';
  return request(path, { method: 'GET' });
};
export const apiGetAppUsers = (appId, page = 1, limit = 50) =>
  request(`/api/developer/users?app_id=${appId}&page=${page}&limit=${limit}`, { method: 'GET' });
export const apiGetUserData = (userId) => request(`/api/developer/user/${userId}`, { method: 'GET' });

3.2 Auth Context (React Native)

import React, { createContext, useContext, useEffect, useState } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { apiLogin, apiRegister, apiGoogleLogin, apiGetProfile } from '../services/authApi';

const AuthContext = createContext(null);

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  const [accessToken, setAccessToken] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    (async () => {
      const token = await AsyncStorage.getItem('access_token');
      if (token) {
        setAccessToken(token);
        await refreshProfile(token);
      }
      setLoading(false);
    })();
  }, []);

  const refreshProfile = async (token) => {
    try {
      const resp = await apiGetProfile(token);
      setUser(resp.data);
    } catch {
      setUser(null);
      setAccessToken(null);
      await AsyncStorage.removeItem('access_token');
    }
  };

  const login = async (payload) => {
    const resp = await apiLogin(payload);
    const token = resp?.data?.access_token || resp?.data?.user_token;
    if (token) {
      setAccessToken(token);
      await AsyncStorage.setItem('access_token', token);
      await refreshProfile(token);
    }
    return resp;
  };

  const register = async (payload) => {
    const resp = await apiRegister(payload);
    const token = resp?.data?.access_token || resp?.data?.user_token;
    if (token) {
      setAccessToken(token);
      await AsyncStorage.setItem('access_token', token);
      await refreshProfile(token);
    }
    return resp;
  };

  const googleLogin = async (idToken) => {
    const resp = await apiGoogleLogin(idToken);
    const token = resp?.data?.access_token || resp?.data?.user_token;
    if (token) {
      setAccessToken(token);
      await AsyncStorage.setItem('access_token', token);
      await refreshProfile(token);
    }
    return resp;
  };

  const logout = async () => {
    setUser(null);
    setAccessToken(null);
    await AsyncStorage.removeItem('access_token');
  };

  return (
    <AuthContext.Provider
      value={{ user, accessToken, loading, login, register, googleLogin, logout, isAuthenticated: !!user }}
    >
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error('useAuth must be used within AuthProvider');
  return ctx;
};

3.3 Google Sign-In (React Native)

npm install @react-native-google-signin/google-signin
import { GoogleSignin } from '@react-native-google-signin/google-signin';
import { useAuth } from '../context/AuthContext';

GoogleSignin.configure({
  webClientId: 'YOUR_WEB_CLIENT_ID_FROM_GOOGLE',
});

function LoginScreen() {
  const { login, googleLogin } = useAuth();

  const handleGoogle = async () => {
    try {
      await GoogleSignin.hasPlayServices();
      const userInfo = await GoogleSignin.signIn();
      const idToken = userInfo.idToken;
      await googleLogin(idToken); // sends to backend /api/auth/google
    } catch (err) {
      console.error('Google sign-in failed', err);
    }
  };

  // ...render buttons, etc.
}

4. Key & Security Guidelines

  • @mspkapps/auth-client is backend-only.
  • API key, secret, and googleClientId live only in backend env vars.
  • Frontend talks to backend over HTTPS (/api/auth/*, /api/user/*, /api/developer/*).
  • Frontend stores only user-level access token (e.g. in localStorage / AsyncStorage).
  • Never expose API key/secret in web or mobile bundles.
  • Developer ID is automatically extracted from API key/secret by the backend - no need to pass it explicitly.

5. API Reference

5.1 Authentication APIs

| Method | Endpoint | Description | |--------|----------|-------------| | authclient.register() | POST /api/auth/register | Register new user | | authclient.login() | POST /api/auth/login | Login user | | authclient.googleAuth() | POST /api/auth/google | Google OAuth login | | authclient.verifyToken() | POST /api/auth/verify-token | Verify access token |

5.2 User Profile APIs

| Method | Endpoint | Description | |--------|----------|-------------| | authclient.getProfile() | GET /api/user/profile | Get user profile | | authclient.updateProfile() | PATCH /api/user/profile | Update user profile |

5.3 Developer Data APIs (NEW)

| Method | Endpoint | Description | |--------|----------|-------------| | authclient.getDeveloperGroups() | GET /api/developer/groups | Get all groups for the authenticated developer | | authclient.getDeveloperApps() | GET /api/developer/apps | Get all apps (with and without groups) | | authclient.getDeveloperApps(123) | GET /api/developer/apps?group_id=123 | Get apps in specific group | | authclient.getDeveloperApps(null) | GET /api/developer/apps?group_id=null | Get apps NOT in any group | | authclient.getAppUsers({ appId, page, limit }) | GET /api/developer/users?app_id=X&page=Y&limit=Z | Get users for specific app (paginated) | | authclient.getUserData(userId) | GET /api/developer/user/:user_id | Get specific user data |

Notes:

  • All user data responses exclude the password field for security
  • User data includes all custom extra fields configured for the app
  • developer_id is automatically extracted from API key & secret - you never pass it manually
  • Pagination is supported for user lists with page and limit parameters

6. Troubleshooting

Frontend gets 4xx/5xx from backend

  • Inspect backend logs; most errors will be AuthError thrown by AuthClient.
  • Make sure backend env vars (MSPK_AUTH_API_KEY, MSPK_AUTH_API_SECRET) are set.

Google login succeeds on client but fails on backend

  • Ensure GOOGLE_CLIENT_ID in backend matches the client ID used on the frontend.
  • Check that the frontend sends credential / id_token to /api/auth/google correctly.

Developer data APIs return 403/404

  • Verify that the API key & secret in your backend env vars are correct.
  • The developer ID is extracted automatically from these credentials.
  • Ensure you're querying data that belongs to the authenticated developer.

App users query returns empty results

  • Verify the app_id belongs to the authenticated developer.
  • Check that the app actually has registered users.

7. Summary

  • Install and initialize @mspkapps/auth-client only in your backend.
  • Implement clean REST endpoints in your backend that call authclient methods.
  • React and React Native frontends call those endpoints with plain HTTP (fetch/axios).
  • This keeps API keys safe and maintains a clean separation between frontend and backend.
  • Use the new Developer Data APIs to build dashboards that display groups, apps, and users.
  • Developer ID is automatically extracted from API credentials - never passed manually.