Wenme OAuth 2.1 Integration
Implement enterprise-grade passwordless authentication in minutes with our secure OAuth 2.1 platform. Full PKCE support, OpenID Connect, and zero passwords.
Documentation
Quick Start
Prerequisites
Integration Code
// 1. Redirect users to Wenme OAuth authorization
const authUrl = new URL('https://identity.wenme.net/oauth/authorize');
authUrl.searchParams.append('client_id', 'YOUR_CLIENT_ID');
authUrl.searchParams.append('redirect_uri', 'YOUR_CALLBACK_URL');
authUrl.searchParams.append('response_type', 'code');
authUrl.searchParams.append('scope', 'openid profile email');
authUrl.searchParams.append('state', generateRandomState());
// 2. PKCE (mandatory in OAuth 2.1)
const codeVerifier = generateCodeVerifier();
const codeChallenge = await generateCodeChallenge(codeVerifier);
authUrl.searchParams.append('code_challenge', codeChallenge);
authUrl.searchParams.append('code_challenge_method', 'S256');
window.location.href = authUrl.toString();Complete OAuth 2.1 Integration Guide
Step 1: Automatic Endpoint Discovery (Recommended)
Wenme supports OpenID Connect Discovery. Simply fetch this endpoint to get all OAuth configuration:
GET https://wenme.net/.well-known/openid-configurationReturns all endpoints, supported scopes, and algorithms in JSON. Use this for automatic configuration.
Step 2: Configure Your OAuth Client
For NextAuth.js or similar libraries:
{
id: "wenme",
name: "Wenme",
type: "oauth",
wellKnown: "https://wenme.net/.well-known/openid-configuration",
authorization: {
params: {
scope: "openid profile email"
}
},
clientId: process.env.WENME_CLIENT_ID,
clientSecret: process.env.WENME_CLIENT_SECRET,
idToken: true,
profile(profile) {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: profile.picture
}
}
}OAuth Endpoints (All Return JSON)
https://identity.wenme.net/oauth/authorizehttps://wenme.net/oauth/tokenhttps://wenme.net/oauth/userinfohttps://identity.wenme.net/.well-known/jwks.jsonhttps://identity.wenme.net/oauth/revokehttps://identity.wenme.net/oauth/introspecthttps://identity.wenme.net/oauth/end_sessionOAuth Authorization URL Format
Complete Authorization URL Structure
All parameters are REQUIRED except where noted. Missing response_type=code will result in an error.
https://identity.wenme.net/oauth/authorize?
client_id=YOUR_CLIENT_ID&
redirect_uri=YOUR_CALLBACK_URL&
response_type=code&
scope=openid+profile+email&
state=RANDOM_STATE&
code_challenge=PKCE_CHALLENGE&
code_challenge_method=S256โ Required Parameters
client_idYour application's client IDredirect_uriMust match registered URI exactlyresponse_typeMust be "code" (OAuth 2.1)scopeSpace-separated permissionsstateRandom string for CSRF protectioncode_challengePKCE challenge (base64url)code_challenge_methodMust be "S256"๐ Available Scopes
openidOpenID Connect authenticationprofileUser's profile informationemailUser's email addressoffline_accessRefresh token for long-lived accessNote: PKCE is mandatory in OAuth 2.1. The implicit grant flow is not supported.
Setup Your Application
Register Your Application
Visit the Organization Dashboard to create your OAuth application.
Store Credentials Securely
Never expose your Client Secret in client-side code!
# .env file (server-side only)WENME_CLIENT_ID=wenme_abc123... WENME_CLIENT_SECRET=secret_xyz789... WENME_REDIRECT_URI=https://yourapp.com/auth/callback
OAuth 2.1 Flow
Full OAuth 2.1 Compliance (RFC 9207)
Wenme implements the latest OAuth 2.1 standard with mandatory security enhancements:
Authorization Request
Redirect user to Wenme authorization endpoint with PKCE parameters.
// 1. Redirect users to Wenme OAuth authorization
const authUrl = new URL('https://identity.wenme.net/oauth/authorize');
authUrl.searchParams.append('client_id', 'YOUR_CLIENT_ID');
authUrl.searchParams.append('redirect_uri', 'YOUR_CALLBACK_URL');
authUrl.searchParams.append('response_type', 'code');
authUrl.searchParams.append('scope', 'openid profile email');
authUrl.searchParams.append('state', generateRandomState());
// 2. PKCE (mandatory in OAuth 2.1)
const codeVerifier = generateCodeVerifier();
const codeChallenge = await generateCodeChallenge(codeVerifier);
authUrl.searchParams.append('code_challenge', codeChallenge);
authUrl.searchParams.append('code_challenge_method', 'S256');
window.location.href = authUrl.toString();Token Exchange
Exchange authorization code for access tokens.
// 3. Exchange authorization code for tokens
const response = await fetch('https://wenme.net/oauth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: 'YOUR_CLIENT_ID',
client_secret: 'YOUR_CLIENT_SECRET',
code: authorizationCode,
redirect_uri: 'YOUR_CALLBACK_URL',
code_verifier: codeVerifier // PKCE verifier (mandatory in OAuth 2.1)
})
});
const tokens = await response.json();
// tokens.access_token, tokens.id_token, tokens.refresh_tokenGet User Information
Use access token to fetch user profile.
// 4. Get user information
const userResponse = await fetch('https://identity.wenme.net/oauth/userinfo', {
headers: {
'Authorization': `Bearer ${tokens.access_token}`
}
});
const user = await userResponse.json();
// user.sub, user.email, user.name, user.picture (avatar URL)PKCE Security
Important Security Notice
PKCE (Proof Key for Code Exchange) is mandatory in OAuth 2.1 for ALL clients, including confidential clients. This prevents authorization code interception attacks and is no longer optional.
PKCE Implementation
// Generate code verifier (43-128 characters)
function generateCodeVerifier() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return btoa(String.fromCharCode(...array))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
// Generate code challenge from verifier
async function generateCodeChallenge(verifier) {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const hash = await crypto.subtle.digest('SHA-256', data);
return btoa(String.fromCharCode(...new Uint8Array(hash)))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}Code Examples
JavaScript Quick Start
// 1. Redirect users to Wenme OAuth authorization
const authUrl = new URL('https://identity.wenme.net/oauth/authorize');
authUrl.searchParams.append('client_id', 'YOUR_CLIENT_ID');
authUrl.searchParams.append('redirect_uri', 'YOUR_CALLBACK_URL');
authUrl.searchParams.append('response_type', 'code');
authUrl.searchParams.append('scope', 'openid profile email');
authUrl.searchParams.append('state', generateRandomState());
// 2. PKCE (mandatory in OAuth 2.1)
const codeVerifier = generateCodeVerifier();
const codeChallenge = await generateCodeChallenge(codeVerifier);
authUrl.searchParams.append('code_challenge', codeChallenge);
authUrl.searchParams.append('code_challenge_method', 'S256');
window.location.href = authUrl.toString();
// 3. Exchange authorization code for tokens
const response = await fetch('https://wenme.net/oauth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: 'YOUR_CLIENT_ID',
client_secret: 'YOUR_CLIENT_SECRET',
code: authorizationCode,
redirect_uri: 'YOUR_CALLBACK_URL',
code_verifier: codeVerifier // PKCE verifier (mandatory in OAuth 2.1)
})
});
const tokens = await response.json();
// tokens.access_token, tokens.id_token, tokens.refresh_token
// 4. Get user information
const userResponse = await fetch('https://identity.wenme.net/oauth/userinfo', {
headers: {
'Authorization': `Bearer ${tokens.access_token}`
}
});
const user = await userResponse.json();
// user.sub, user.email, user.name, user.picture (avatar URL)Logout & Token Management
Three Ways to Handle Logout
1. Token Revocation (Recommended)
Immediately invalidate tokens on the authorization server. Best for security.
POST https://identity.wenme.net/oauth/revoke
Content-Type: application/x-www-form-urlencoded
token=ACCESS_TOKEN&
token_type_hint=access_token&
client_id=YOUR_CLIENT_ID&
client_secret=YOUR_CLIENT_SECRET2. End Session (OpenID Connect)
Logs user out from Wenme and optionally redirects back to your app.
GET https://identity.wenme.net/oauth/end_session?
id_token_hint=ID_TOKEN&
post_logout_redirect_uri=https://yourapp.com/logout-complete&
state=xyz1233. Client-Side Only
Simply delete tokens from local storage. Token remains valid until expiry.
// JavaScript
localStorage.removeItem('access_token');
localStorage.removeItem('id_token');
localStorage.removeItem('refresh_token');
// Redirect to login or home
window.location.href = '/';Logout Implementation Examples
NextAuth.js with Token Revocation
// pages/api/auth/[...nextauth].js
import NextAuth from "next-auth"
export default NextAuth({
// ... provider config
events: {
async signOut({ token }) {
// Revoke token when user signs out
if (token?.accessToken) {
await fetch('https://identity.wenme.net/oauth/revoke', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
token: token.accessToken,
token_type_hint: 'access_token',
client_id: process.env.WENME_CLIENT_ID,
client_secret: process.env.WENME_CLIENT_SECRET,
}),
});
}
},
},
callbacks: {
async jwt({ token, account }) {
if (account) {
token.accessToken = account.access_token
token.idToken = account.id_token
}
return token
},
},
})Express.js with End Session
// Express logout route
app.post('/logout', async (req, res) => {
const { id_token } = req.session;
// Clear server session
req.session.destroy();
// Build Wenme logout URL
const logoutUrl = new URL('https://identity.wenme.net/oauth/end_session');
logoutUrl.searchParams.append('id_token_hint', id_token);
logoutUrl.searchParams.append(
'post_logout_redirect_uri',
'https://yourapp.com/logout-complete'
);
logoutUrl.searchParams.append('state', generateRandomState());
// Redirect to Wenme logout
res.redirect(logoutUrl.toString());
});
// Logout complete callback
app.get('/logout-complete', (req, res) => {
// Verify state parameter
if (req.query.state !== expectedState) {
return res.status(400).send('Invalid state');
}
res.redirect('/');
});React SPA with Token Revocation
// React logout hook
import { useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
export function useLogout() {
const navigate = useNavigate();
const logout = useCallback(async () => {
const token = localStorage.getItem('access_token');
if (token) {
// Revoke token
try {
await fetch('https://identity.wenme.net/oauth/revoke', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
token,
token_type_hint: 'access_token',
client_id: process.env.REACT_APP_WENME_CLIENT_ID,
// Note: Don't include client_secret in frontend apps
}),
});
} catch (error) {
console.error('Token revocation failed:', error);
}
}
// Clear local storage
localStorage.removeItem('access_token');
localStorage.removeItem('id_token');
localStorage.removeItem('refresh_token');
// Redirect to login
navigate('/login');
}, [navigate]);
return logout;
}Token Introspection
Check if a token is still valid without making an API call.
POST https://identity.wenme.net/oauth/introspect
Content-Type: application/x-www-form-urlencoded
token=ACCESS_TOKEN&
token_type_hint=access_token&
client_id=YOUR_CLIENT_ID&
client_secret=YOUR_CLIENT_SECRETResponse (Active Token):
{
"active": true,
"scope": "openid profile email",
"client_id": "your_client_id",
"username": "user@example.com",
"exp": 1640995200
}Response (Inactive/Invalid Token):
{
"active": false
}API Endpoints
| Endpoint | Method | Description |
|---|---|---|
| /oauth/authorize | GET | OAuth authorization endpoint |
| /oauth/token | POST | Exchange code for tokens |
| /api/user/profile | GET | Get user information |
| /oauth/revoke | POST | Revoke access token |
| /.well-known/openid-configuration | GET | OpenID Connect discovery |
| /.well-known/jwks.json | GET | JSON Web Key Set |
API Keys (Server-to-Server)
API keys enable autonomous server-to-server calls without user sessions. Perfect for backend services, cron jobs, and automated integrations that need to call Wenme APIs programmatically.
When to Use API Keys
- Autonomous operations: Backend services that need to send invitations, manage members, etc.
- Scheduled tasks: Cron jobs that sync users or send bulk invitations
- No user context: Operations that don't have a logged-in user session
- Service-to-service: Your server calling Wenme APIs directly
Note: API keys bypass session cookies and CSRF tokens. They authenticate the application, not a user.
API Key Format
API keys follow a consistent format for easy identification:
wm_pk_<32 random hex characters> Example: wm_pk_ab1234567890abcdef1234567890abcd
wm_pk_- Wenme API key prefix- First 2 chars after prefix are used for key lookup (prefix)
- Full key is hashed with bcrypt (never stored in plaintext)
Using API Keys
Include the API key in the Authorization header:
Request Example
curl -X POST https://identity.wenme.net/api/tenant/invite \
-H "Authorization: Bearer wm_pk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "your-organization-uuid",
"email": "newuser@example.com",
"role": "member"
}'JavaScript/TypeScript Example
const response = await fetch('https://identity.wenme.net/api/tenant/invite', {
method: 'POST',
headers: {
'Authorization': 'Bearer wm_pk_your_api_key_here',
'Content-Type': 'application/json',
},
body: JSON.stringify({
tenant_id: 'your-organization-uuid',
email: 'newuser@example.com',
role: 'member'
})
});
const result = await response.json();
console.log(result);API Key Management Endpoints
Create, list, and revoke API keys (requires session auth)
| Endpoint | Method | Description |
|---|---|---|
| /api/keys | POST | Create new API key |
| /api/keys/tenant/:tenantId | GET | List API keys for organization |
| /api/keys/:keyId | DELETE | Revoke an API key |
Creating an API Key
Create API keys via the management API or the Wenme console.
POST /api/keys
{
"name": "PV Production Integration",
"tenant_id": "your-organization-uuid",
"scopes": ["invite:write", "invite:read"],
"description": "API key for automated user invitations",
"expires_in_days": 365 // Optional, 0 = never expires
}Response (Success)
{
"success": true,
"message": "API key created successfully. Save this key - it won't be shown again!",
"api_key": {
"id": "key-uuid",
"name": "PV Production Integration",
"key": "wm_pk_ab1234567890abcdef1234567890abcd", // Only shown once!
"key_prefix": "wm_pk_ab",
"scopes": ["invite:write", "invite:read"],
"expires_at": "2026-12-04T00:00:00Z",
"created_at": "2025-12-04T00:00:00Z"
}
}Important: The full API key is only shown once at creation time. Store it securely immediately. If lost, you must create a new key.
Available Scopes
API keys use scope-based permissions to control access:
| Scope | Description |
|---|---|
| invite:write | Send team invitations |
| invite:read | List/view invitations |
| members:read | List organization members |
| members:write | Manage organization members |
| org:read | Read organization details |
| org:write | Update organization settings |
| users:read | Read user profiles |
| apps:read | List OAuth applications |
| apps:write | Manage OAuth applications |
| * | Full access (all scopes) |
Security Features
- โRate Limiting: Default 60 requests/minute, 10,000 requests/day per key
- โIP Whitelisting: Optionally restrict keys to specific IP addresses/CIDRs
- โTenant Binding: Keys are bound to a specific organization and cannot access other orgs
- โAudit Logging: All API key usage is logged for compliance and debugging
- โKey Expiration: Optional expiration dates for time-limited access
- โInstant Revocation: Keys can be revoked immediately if compromised
Error Responses
Invalid API Key (401)
{
"error": "invalid_api_key",
"message": "Invalid API key"
}Insufficient Scope (403)
{
"error": "insufficient_scope",
"message": "API key does not have required scope: invite:write",
"required_scope": "invite:write"
}Tenant Mismatch (403)
{
"error": "tenant_mismatch",
"message": "API key can only invite to its own organization"
}Rate Limit Exceeded (429)
{
"error": "invalid_api_key",
"message": "Rate limit exceeded"
}Organization & Team APIs
APIs for managing organizations, team members, and invitations. Supports both session-based authentication (browser) and API key authentication (server-to-server).
| Endpoint | Method | Description |
|---|---|---|
| /api/tenant/invite | POST | Send team invitation email |
| /api/tenant/:tenantId/invitations | GET | List pending invitations for organization |
| /api/tenant/invitation/:invitationId | DELETE | Cancel pending invitation |
| /api/invitation/accept | POST | Accept invitation (public endpoint) |
| /api/invitation/send-magic-link | POST | Send magic link for invitation acceptance |
| /api/tenant/:tenantId/members | GET | List organization members |
| /api/user/organizations | GET | List user's organizations |
POST /api/tenant/invite
Send an invitation to join an organization. Requires admin or owner role.
Request Headers
Content-Type: application/json Cookie: auth_session=<session_token> X-CSRF-Token: <csrf_token>
Request Body
{
"tenant_id": "uuid-of-organization",
"email": "newuser@example.com",
"role": "member" // Options: owner, admin, member, viewer, billing
}Response (Success)
{
"success": true,
"message": "Invitation sent successfully",
"invitation": {
"id": "invitation-uuid",
"email": "newuser@example.com",
"role": "member",
"status": "pending",
"expires_at": "2025-12-11T00:00:00Z"
}
}Note: Invitations expire after 7 days. The invitee receives an email with a magic link to accept the invitation.
User Profile APIs
APIs for user profile management. All endpoints require authentication.
| Endpoint | Method | Description |
|---|---|---|
| /api/profile | GET | Get current user's profile |
| /api/profile | PUT | Update user profile |
| /api/profile/avatar | POST | Upload profile picture |
| /api/profile/username | PUT | Change username |
| /api/profile/email/change | POST | Request email change (requires passkey verification) |
| /api/user/:username | GET | Get public profile by username |
GET /api/profile Response
{
"success": true,
"user": {
"id": "uuid",
"email": "user@example.com",
"username": "johndoe",
"name": "John Doe",
"avatar_url": "https://storage.wenme.net/...",
"bio": "Software developer",
"location": "Dhaka, Bangladesh",
"website": "https://example.com",
"totp_enabled": true,
"passkey_enabled": true,
"email_verified": true,
"phone_number": "+880...",
"date_of_birth": "1990-01-01",
"linkedin_url": "https://linkedin.com/in/...",
"github_url": "https://github.com/...",
"created_at": "2025-01-01T00:00:00Z"
}
}Authentication APIs
APIs for passwordless authentication including passkeys, TOTP, and magic links.
| Endpoint | Method | Description |
|---|---|---|
| Passkey (WebAuthn) Endpoints | ||
| /api/passkey/register/start | POST | Begin passkey registration |
| /api/passkey/register/finish | POST | Complete passkey registration |
| /api/passkey/authenticate/start | POST | Begin passkey authentication |
| /api/passkey/authenticate/finish | POST | Complete passkey authentication |
| /api/passkey/list | GET | List user's registered passkeys |
| /api/passkey/:id | DELETE | Remove a passkey |
| TOTP/Authenticator Endpoints | ||
| /api/auth/totp/generate | POST | Generate QR code and backup codes for setup |
| /api/auth/totp/verify | POST | Verify and enable authenticator |
| /api/auth/totp/disable | POST | Disable authenticator (requires code) |
| /api/auth/totp/validate | POST | Validate TOTP code during login (public) |
| /api/auth/mfa/verify | POST | Verify MFA code and create session |
| Magic Link Endpoints | ||
| /auth/magic-link | POST | Send magic link email (body: email) |
| /api/auth/verify-magic-link | POST | Verify magic link token and create session |
| /api/auth/verify-magic-link-redirect | GET | Verify magic link with redirect (from email links) |
| Session & Security Endpoints | ||
| /api/auth/methods/check | POST | Check available auth methods for user (public) |
| /api/auth/csrf-token | GET | Get CSRF token for authenticated requests |
| /api/sessions | GET | List active sessions |
| /api/sessions/:id/revoke | POST | Revoke a specific session |
| /auth/logout | POST/GET | Logout and clear session |
Base URL: All authentication APIs are served from https://identity.wenme.net
OAuth Scopes
| Scope | Description | Claims Returned |
|---|---|---|
| openid | Required for OIDC | sub |
| profile | User profile information | name, picture, given_name, family_name, preferred_username, updated_at |
| User email address | email, email_verified | |
| phone | User phone number | phone_number, phone_number_verified |
| address | User address | address (structured object) |
| offline_access | Refresh token | Enables refresh_token in token response |
Security Note: External applications only receive standard OIDC claims. Internal platform data (roles, permissions, tenant IDs) is never exposed to third-party applications.
Domain Configuration
App Domain Feature
Configure your application domain to automatically manage redirect URIs. Set your app domain once, and all standard OAuth callback paths will be configured.
โข https://newsforge.news/callback
โข https://newsforge.news/auth/callback
โข https://newsforge.news/oauth/callback
โข https://newsforge.news/api/auth/callback
โข https://newsforge.news/signin-callback
CNAME Support
Wenme OAuth fully supports CNAME records for custom domains. Use subdomains that point to your actual servers while maintaining a consistent brand.
Example CNAME Setup:
api.newsforge.news CNAME newsforge-api.aws.com
https://api.newsforge.news/oauth/callback โ
SSL Certificate Required
Ensure valid SSL certificates for all CNAME domains
Use CNAME Domain in URIs
Register redirect URIs with the CNAME domain, not the target
Multiple Variations Supported
Register all domain variations (www, subdomains, etc.)
Security Best Practices
OAuth 2.1 Security Requirements
Always use PKCE
PKCE is mandatory for all OAuth 2.1 flows, even for confidential clients
Validate state parameter
Always verify the state parameter to prevent CSRF attacks
Use HTTPS everywhere
All redirect URIs must use HTTPS in production (localhost allowed for development)
Secure token storage
Never store tokens in localStorage or sessionStorage. Use secure HTTP-only cookies or server-side storage
Rotate refresh tokens
Always exchange refresh tokens for new ones to detect token replay attacks
Validate redirect URIs
Register all redirect URIs and use exact string matching (no wildcards)
Common Security Mistakes to Avoid
- โ Using implicit flow (removed in OAuth 2.1)
- โ Storing client secrets in frontend code
- โ Not validating the state parameter
- โ Using localStorage for token storage
- โ Not implementing PKCE
- โ Using wildcard redirect URIs
- โ Not using HTTPS in production
- โ Long-lived access tokens (use short expiry + refresh tokens)
Testing Your Integration
Test Credentials
You can create a test application in the dashboard for development purposes.
Go to Organization DashboardOAuth Flow Tester
Use our built-in OAuth flow tester to validate your configuration.
Need Help?
Developer Support
Our team is here to help you integrate Wenme authentication into your applications.