watchman-auth
v1.0.0
Published
JavaScript SDK for Watchman Authentication - Auth0-compatible interface
Downloads
4
Maintainers
Readme
@watchman/auth-js
JavaScript/TypeScript SDK for Watchman Authentication Service. Drop-in replacement for Auth0 with a familiar API.
Features
- 🔐 OAuth2 / OpenID Connect authentication
- 🔄 Automatic token refresh
- 🛡️ PKCE (Proof Key for Code Exchange) for enhanced security
- ⚛️ React hooks and components
- 📦 TypeScript support
- 💾 Flexible storage (localStorage or in-memory)
- 🎯 Auth0-compatible API for easy migration
Installation
npm install @watchman/auth-js
# or
yarn add @watchman/auth-js
# or
pnpm add @watchman/auth-jsQuick Start
React Application
import React from 'react';
import ReactDOM from 'react-dom/client';
import { WatchmanProvider } from '@watchman/auth-js';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<WatchmanProvider
domain="https://auth.yourapp.com"
clientId="your-client-id"
authorizationParams={{
redirect_uri: window.location.origin,
audience: "https://api.yourapp.com",
scope: "openid profile email offline_access"
}}
useRefreshTokens={true}
cacheLocation="localstorage"
>
<App />
</WatchmanProvider>
);Using the Hook in Components
import { useWatchman } from '@watchman/auth-js';
function MyComponent() {
const {
isAuthenticated,
isLoading,
user,
loginWithRedirect,
logout,
getAccessTokenSilently,
} = useWatchman();
if (isLoading) {
return <div>Loading...</div>;
}
if (!isAuthenticated) {
return (
<button onClick={() => loginWithRedirect()}>
Log In
</button>
);
}
return (
<div>
<h1>Welcome {user?.name || user?.email}!</h1>
<p>Email: {user?.email}</p>
<p>Roles: {user?.roles?.join(', ')}</p>
<button onClick={() => logout({ returnTo: window.location.origin })}>
Log Out
</button>
</div>
);
}Protected Routes with React Router
import { Navigate, useLocation } from 'react-router-dom';
import { useWatchman } from '@watchman/auth-js';
function RequireAuth({ children }) {
const { isAuthenticated, isLoading, loginWithRedirect } = useWatchman();
const location = useLocation();
if (isLoading) {
return <div>Loading authentication...</div>;
}
if (!isAuthenticated) {
// Trigger login and preserve the location they were trying to access
loginWithRedirect({
appState: {
returnTo: location.pathname + location.search + location.hash
}
});
return null;
}
return children;
}
// Usage in routes
<Route
path="/protected"
element={
<RequireAuth>
<ProtectedPage />
</RequireAuth>
}
/>Handling Redirect Callback
import { useNavigate } from 'react-router-dom';
import { WatchmanProvider } from '@watchman/auth-js';
function Auth0ProviderWithNavigate({ children }) {
const navigate = useNavigate();
const onRedirectCallback = (appState) => {
// Clean up URL parameters
const searchParams = new URLSearchParams(window.location.search);
searchParams.delete('code');
searchParams.delete('state');
const cleanSearch = searchParams.toString();
const cleanUrl = window.location.pathname +
(cleanSearch ? `?${cleanSearch}` : '') +
window.location.hash;
// Navigate to the returnTo URL or current clean URL
const targetUrl = appState?.returnTo || cleanUrl || '/';
window.history.replaceState({}, document.title, cleanUrl);
setTimeout(() => {
navigate(targetUrl, { replace: true });
}, 100);
};
return (
<WatchmanProvider
domain="https://auth.yourapp.com"
clientId="your-client-id"
authorizationParams={{
redirect_uri: window.location.origin
}}
onRedirectCallback={onRedirectCallback}
cacheLocation="localstorage"
useRefreshTokens={true}
>
{children}
</WatchmanProvider>
);
}Making Authenticated API Calls
import { useWatchman } from '@watchman/auth-js';
function DataComponent() {
const { getAccessTokenSilently } = useWatchman();
const [data, setData] = useState(null);
const fetchData = async () => {
try {
// Get access token
const token = await getAccessTokenSilently();
// Make authenticated request
const response = await fetch('https://api.yourapp.com/data', {
headers: {
Authorization: `Bearer ${token}`,
},
});
const data = await response.json();
setData(data);
} catch (error) {
console.error('Error fetching data:', error);
}
};
useEffect(() => {
fetchData();
}, []);
return <div>{/* Render your data */}</div>;
}Vanilla JavaScript (No React)
import { WatchmanClient } from '@watchman/auth-js';
const watchman = new WatchmanClient({
domain: 'https://auth.yourapp.com',
clientId: 'your-client-id',
redirectUri: window.location.origin,
scope: 'openid profile email',
cacheLocation: 'localstorage',
useRefreshTokens: true,
});
// Check authentication status
if (watchman.isAuthenticated()) {
const user = watchman.getUser();
console.log('Logged in as:', user.email);
} else {
// Login
watchman.loginWithRedirect();
}
// Get access token
async function callAPI() {
try {
const token = await watchman.getAccessTokenSilently();
const response = await fetch('https://api.yourapp.com/data', {
headers: {
Authorization: `Bearer ${token}`,
},
});
return await response.json();
} catch (error) {
console.error('API call failed:', error);
}
}
// Logout
function logout() {
watchman.logout({ returnTo: window.location.origin });
}API Reference
WatchmanProvider Props
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| domain | string | Yes | Your Watchman auth server URL |
| clientId | string | Yes | Your application's client ID |
| authorizationParams | object | No | Authorization parameters (redirect_uri, audience, scope) |
| cacheLocation | 'memory' | 'localstorage' | No | Where to store tokens (default: 'localstorage') |
| useRefreshTokens | boolean | No | Enable refresh token rotation (default: true) |
| onRedirectCallback | function | No | Callback after successful authentication |
useWatchman Hook
Returns an object with:
| Property | Type | Description |
|----------|------|-------------|
| isAuthenticated | boolean | Whether the user is authenticated |
| isLoading | boolean | Whether the auth state is being determined |
| user | User | undefined | Current user information |
| error | Error | undefined | Any authentication error |
| loginWithRedirect | function | Redirect to login page |
| logout | function | Log out the current user |
| getAccessTokenSilently | function | Get access token (with auto-refresh) |
User Object
{
sub: string; // User ID
email?: string; // Email address
email_verified?: boolean; // Email verification status
name?: string; // Full name
picture?: string; // Profile picture URL
roles?: string[]; // User roles
}Migration from Auth0
This SDK is designed to be a drop-in replacement for @auth0/auth0-react. Simply:
- Replace
@auth0/auth0-reactwith@watchman/auth-js - Change
Auth0ProvidertoWatchmanProvider - Update the
domainandclientIdto your Watchman configuration - Everything else stays the same!
- import { Auth0Provider, useAuth0 } from '@auth0/auth0-react';
+ import { WatchmanProvider, useWatchman } from '@watchman/auth-js';
- <Auth0Provider
+ <WatchmanProvider
domain="your-watchman-domain.com"
clientId="your-client-id"
>
<App />
- </Auth0Provider>
+ </WatchmanProvider>Or use the Auth0-compatible aliases:
import { Auth0Provider, useAuth0 } from '@watchman/auth-js';
// These are aliases to WatchmanProvider and useWatchmanConfiguration Examples
Different Scopes
<WatchmanProvider
domain="https://auth.yourapp.com"
clientId="your-client-id"
authorizationParams={{
redirect_uri: window.location.origin,
scope: "openid profile email roles offline_access"
}}
>
<App />
</WatchmanProvider>With Audience (for API access)
<WatchmanProvider
domain="https://auth.yourapp.com"
clientId="your-client-id"
authorizationParams={{
redirect_uri: window.location.origin,
audience: "https://api.yourapp.com",
scope: "openid profile email read:data write:data"
}}
>
<App />
</WatchmanProvider>Memory-only Storage (No Persistence)
<WatchmanProvider
domain="https://auth.yourapp.com"
clientId="your-client-id"
cacheLocation="memory"
useRefreshTokens={false}
>
<App />
</WatchmanProvider>Security Best Practices
- Always use HTTPS in production for your Watchman domain
- Use PKCE (enabled by default in this SDK)
- Request minimal scopes - only what your app needs
- Use refresh tokens for long-lived sessions
- Validate tokens on your backend API
- Don't expose client secrets in frontend code (this SDK doesn't require them)
Troubleshooting
Infinite redirect loop
Make sure your redirect_uri matches exactly what's configured in your Watchman client settings.
Token not refreshing
Ensure you've requested the offline_access scope and useRefreshTokens is true.
User object is undefined
The user object is only available after isAuthenticated is true and isLoading is false.
License
MIT
Support
For issues and questions:
- GitHub Issues: https://github.com/your-org/watchman
- Documentation: https://docs.yourapp.com/watchman
Made with ❤️ for developers who value control over their authentication
