@nightmar3/uauth-core
v1.0.6
Published
Universal authentication SDK that works with any backend
Maintainers
Readme
@nightmar3/uauth-core
Core authentication SDK that works with any backend implementing the Universal Auth contract.
[!IMPORTANT] Backend Required: This SDK requires a backend API. See Backend Requirements for the complete API contract, or use our Backend Implementation Guide for step-by-step instructions.
Setup Guide
Step 1: Install the Package
npm install @nightmar3/uauth-coreStep 2: Create Auth Instance
import { createAuth } from '@nightmar3/uauth-core'
const auth = createAuth({
baseURL: 'https://api.yourapp.com/auth',
storage: localStorage, // or sessionStorage, AsyncStorage, custom
})Step 3: Sign In
const result = await auth.signIn('password', {
email: '[email protected]',
password: 'secret123',
})
if (result.ok) {
console.log('User:', result.data.user)
console.log('Tokens stored automatically')
}Step 4: Get Session
const session = await auth.session()
if (session.ok) {
console.log('Current user:', session.data.user)
}Step 5: Sign Out
await auth.signOut()That's it! You have full authentication support.
Use Cases
Email/Password Authentication
// Sign in
const result = await auth.signIn('password', {
email: '[email protected]',
password: 'secret',
})
// Sign up
const result = await auth.signUp({
email: '[email protected]',
password: 'secure123',
name: 'New User',
})OAuth2 Authentication (Optional)
OAuth is completely optional. If you don't need OAuth, skip this section - no extra network requests will be made.
To add OAuth support:
1. Install the OAuth2 plugin:
import { createAuth, createOAuth2Plugin } from '@nightmar3/uauth-core'
const auth = createAuth({
baseURL: 'https://api.yourapp.com/auth',
storage: localStorage,
})
// Install OAuth2 plugin
const oauth2Plugin = createOAuth2Plugin()
await auth.plugin('oauth2', oauth2Plugin)2. Get available providers:
// Load providers from backend
const providers = await oauth2Plugin.loadProviders()
console.log(providers)
// [{ name: 'google', displayName: 'Google' }, { name: 'github', displayName: 'GitHub' }]3. Sign in with OAuth:
// Popup flow (recommended for SPAs)
const result = await oauth2Plugin.signInWithPopup({ provider: 'google' })
if (result.ok) {
console.log('User:', result.data.user)
}
// Redirect flow (for SSR or when popups are blocked)
oauth2Plugin.signInWithRedirect({ provider: 'google' })4. Handle OAuth callback (for redirect flow):
// On your callback page
const result = await oauth2Plugin.handleCallback()
if (result.ok) {
console.log('User:', result.data.user)
window.location.href = '/dashboard'
}React/Next.js Usage
When using with @nightmar3/uauth-react or @nightmar3/uauth-next, pass plugins to the AuthProvider:
import { createAuth, createOAuth2Plugin } from '@nightmar3/uauth-core'
import { AuthProvider, useOAuth } from '@nightmar3/uauth-react'
const auth = createAuth({ baseURL: '...' })
const plugins = [createOAuth2Plugin()]
function App() {
return (
<AuthProvider auth={auth} plugins={plugins}>
<YourApp />
</AuthProvider>
)
}
function LoginPage() {
const { providers, signInWithOAuth } = useOAuth()
return (
<div>
{providers.map(p => (
<button key={p.name} onClick={() => signInWithOAuth(p.name)}>
Continue with {p.displayName}
</button>
))}
</div>
)
}Token Management
// Get access token
const token = await auth.getToken()
// Check authentication status
const isAuth = await auth.isAuthenticated()
// Refresh tokens manually
await auth.refresh()Custom Storage Adapters
// React Native with AsyncStorage
import AsyncStorage from '@react-native-async-storage/async-storage'
const auth = createAuth({
baseURL: '...',
storage: AsyncStorage, // Works directly!
})
// Custom storage
const customStorage = {
getItem: (key) => myDB.get(key),
setItem: (key, value) => myDB.set(key, value),
removeItem: (key) => myDB.delete(key),
}
const auth = createAuth({
baseURL: '...',
storage: customStorage,
})Backend Requirements
The SDK expects a backend API that implements the following endpoints. You can use our FastAPI reference implementation or build your own.
Endpoints
| Method | Path | Description | Request Body | Response Data |
|--------|------|-------------|--------------|---------------|
| POST | /sign-in/password | Sign in with email/password | { email, password } | { user, tokens } |
| POST | /sign-in/oauth2 | Exchange OAuth code for tokens | { provider, code, redirect_uri } | { user, tokens } |
| POST | /sign-up | Create new account | { email, password, name? } | { user, tokens } |
| DELETE| /session | Sign out (revoke tokens) | - | { ok: boolean } |
| GET | /session | Get current user session | - | { user } |
| POST | /token/refresh | Refresh access token | { refresh_token } | { tokens } |
| GET | /providers | List available OAuth providers | - | { providers } |
Response Format
All API responses must follow this envelope structure:
interface ApiResponse<T> {
ok: boolean
data: T | null
error: {
code: string
message: string
details?: any
} | null
}Token Structure
The backend must return tokens in this format:
interface AuthTokens {
access_token: string
refresh_token: string
expires_in: number // seconds
}OAuth Provider Response (Optional)
If implementing OAuth support, the /providers endpoint must return:
interface OAuth2Provider {
name: string
displayName: string
clientId: string
authorizationUrl: string
scope?: string
}Example:
{
"ok": true,
"data": {
"providers": [
{
"name": "google",
"displayName": "Google",
"clientId": "your-google-client-id",
"authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth",
"scope": "openid email profile"
}
]
},
"error": null
}API Reference
createAuth(config)
Create an auth instance.
interface AuthConfig {
baseURL: string // Required: Your auth backend URL
storage?: StorageAdapter // Optional: Storage implementation
fetch?: typeof fetch // Optional: Custom fetch function
storageKeyPrefix?: string // Optional: Prefix for storage keys
onTokenRefresh?: (tokens) => void // Optional: Called when tokens refresh
onAuthError?: (error) => void // Optional: Called on auth errors
}
const auth = createAuth({
baseURL: 'https://api.yourapp.com/auth',
storage: localStorage,
storageKeyPrefix: 'myapp_',
onTokenRefresh: (tokens) => {
console.log('Tokens refreshed')
},
onAuthError: (error) => {
console.error('Auth error:', error.message)
},
})auth.signIn(method, payload)
Sign in with various methods.
// Password authentication
await auth.signIn('password', {
email: '[email protected]',
password: 'secret',
})
// OAuth2 (with code from callback)
await auth.signIn('oauth2', {
provider: 'google',
code: 'auth_code',
})Returns: Promise<ApiResponse<SignInData>>
auth.signUp(payload)
Create a new account.
const result = await auth.signUp({
email: '[email protected]',
password: 'secure123',
name: 'New User',
})Returns: Promise<ApiResponse<SignInData>>
auth.signOut()
Sign out and clear tokens.
await auth.signOut()Returns: Promise<ApiResponse<{ ok: boolean }>>
auth.session()
Get the current session.
const result = await auth.session()
if (result.ok) {
console.log('User:', result.data.user)
}Returns: Promise<ApiResponse<SessionData>>
auth.refresh()
Manually refresh tokens.
const result = await auth.refresh()
if (result.ok) {
console.log('New tokens:', result.data.tokens)
}Note: Tokens are automatically refreshed on 401 responses.
auth.isAuthenticated()
Check if user is authenticated.
const isAuth = await auth.isAuthenticated()Returns: Promise<boolean>
auth.getToken()
Get the current access token.
const token = await auth.getToken()
if (token) {
fetch('/api/data', {
headers: { Authorization: `Bearer ${token}` },
})
}Returns: Promise<string | null>
auth.plugin(name, plugin)
Install a plugin.
await auth.plugin('oauth2', createOAuth2Plugin())Low-Level Utilities
refreshTokenRequest(baseURL, refreshToken, fetchFn?)
Low-level utility to make a token refresh request. This is used internally by auth.refresh() but is exported for advanced use cases (e.g., server-side refresh).
import { refreshTokenRequest } from '@nightmar3/uauth-core'
const result = await refreshTokenRequest(
'https://api.yourapp.com/auth',
'refresh_token_value'
)
if (result.ok) {
console.log('New tokens:', result.data.tokens)
}Parameters:
baseURL- The base URL of the auth APIrefreshToken- The refresh token to usefetchFn- Optional custom fetch function (defaults to globalfetch)
Returns: Promise<ApiResponse<{ tokens: AuthTokens }>>
OAuth2 Plugin API
createOAuth2Plugin()
Create the OAuth2 plugin.
import { createOAuth2Plugin } from '@nightmar3/uauth-core'
const oauth2Plugin = createOAuth2Plugin()
await auth.plugin('oauth2', oauth2Plugin)oauth2Plugin.loadProviders()
Load available OAuth providers from backend.
const providers = await oauth2Plugin.loadProviders()
// [{ name: 'google', displayName: 'Google' }, ...]Returns: Promise<OAuth2Provider[]>
oauth2Plugin.signInWithPopup(options)
Sign in using popup window.
const result = await oauth2Plugin.signInWithPopup({
provider: 'google',
redirectUri: '/auth/callback', // Optional
})
if (result.ok) {
console.log('User:', result.data.user)
}Returns: Promise<ApiResponse<SignInData>>
oauth2Plugin.signInWithRedirect(options)
Sign in using redirect.
oauth2Plugin.signInWithRedirect({
provider: 'github',
redirectUri: '/auth/callback', // Optional
})
// Page redirects to OAuth provideroauth2Plugin.handleCallback()
Handle OAuth callback (for redirect flow).
// On your callback page
const result = await oauth2Plugin.handleCallback()
if (result.ok) {
window.location.href = '/dashboard'
}Returns: Promise<ApiResponse<SignInData>>
Response Format
All methods return a standard response envelope:
interface ApiResponse<T> {
ok: boolean
data: T | null
error: ApiError | null
}
interface ApiError {
code: string
message: string
details?: Record<string, any>
}Success:
{
ok: true,
data: {
user: { id: '...', email: '...', name: '...' },
tokens: { access_token: '...', refresh_token: '...', expires_in: 3600 }
},
error: null
}Error:
{
ok: false,
data: null,
error: {
code: 'INVALID_CREDENTIALS',
message: 'Email or password is incorrect'
}
}Error Handling
const result = await auth.signIn('password', { email, password })
if (!result.ok) {
switch (result.error?.code) {
case 'INVALID_CREDENTIALS':
alert('Wrong email or password')
break
case 'NETWORK_ERROR':
alert('Connection failed')
break
default:
alert(result.error?.message)
}
}Automatic Token Refresh
The SDK automatically refreshes tokens when:
- Access token is expired (checked before requests)
- A request returns 401 Unauthorized
How it works:
- Request fails with 401
- SDK calls
/token/refreshwith refresh token - New tokens stored automatically
- Original request retried with new token
- If refresh fails, user is logged out
You don't need to handle this manually.
Storage Adapters
Built-in Adapters
import {
LocalStorageAdapter,
SessionStorageAdapter,
MemoryStorageAdapter,
} from '@nightmar3/uauth-core'
// localStorage (default)
const auth = createAuth({
baseURL: '...',
storage: new LocalStorageAdapter(),
})
// sessionStorage (cleared on tab close)
const auth = createAuth({
baseURL: '...',
storage: new SessionStorageAdapter(),
})
// In-memory (for testing or SSR)
const auth = createAuth({
baseURL: '...',
storage: new MemoryStorageAdapter(),
})Custom Storage Adapter
interface StorageAdapter {
getItem(key: string): Promise<string | null> | string | null
setItem(key: string, value: string): Promise<void> | void
removeItem(key: string): Promise<void> | void
}
// Example: AsyncStorage for React Native
import AsyncStorage from '@react-native-async-storage/async-storage'
const auth = createAuth({
baseURL: '...',
storage: AsyncStorage, // Works directly!
})Plugin System
Extend the SDK with custom plugins.
const customPlugin = {
name: 'my-plugin',
version: '1.0.0',
install({ client, core, sdk }) {
// Add methods to SDK
sdk.customMethod = async () => {
return client.req('/custom-endpoint')
}
},
}
await auth.plugin('my-plugin', customPlugin)
// Now available
await auth.customMethod()TypeScript Support
Full TypeScript support with generic user types:
interface MyUser {
id: string
email: string
role: 'admin' | 'user'
customField: string
}
const auth = createAuth<MyUser>({
baseURL: '...',
})
const result = await auth.session()
if (result.ok) {
// result.data.user is typed as MyUser
console.log(result.data.user.role)
}React Native
Works out of the box with React Native:
import AsyncStorage from '@react-native-async-storage/async-storage'
import { createAuth } from '@nightmar3/uauth-core'
const auth = createAuth({
baseURL: 'https://api.yourapp.com/auth',
storage: AsyncStorage,
})License
MIT
