Wenme
Developer Documentation

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.

OAuth 2.1
PKCE Required
Passwordless
OpenID Connect

Quick Start

Prerequisites

1Create an OAuth app in Wenme Dashboard
2Get your Client ID and Secret
3Configure redirect URIs
4Implement PKCE (mandatory)

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-configuration

Returns 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/authorize
https://wenme.net/oauth/token
https://wenme.net/oauth/userinfo
https://identity.wenme.net/.well-known/jwks.json
https://identity.wenme.net/oauth/revoke
https://identity.wenme.net/oauth/introspect
https://identity.wenme.net/oauth/end_session

OAuth 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 ID
redirect_uriMust match registered URI exactly
response_typeMust be "code" (OAuth 2.1)
scopeSpace-separated permissions
stateRandom string for CSRF protection
code_challengePKCE challenge (base64url)
code_challenge_methodMust be "S256"

๐Ÿ“‹ Available Scopes

openidOpenID Connect authentication
profileUser's profile information
emailUser's email address
offline_accessRefresh token for long-lived access

Note: PKCE is mandatory in OAuth 2.1. The implicit grant flow is not supported.

Setup Your Application

1

Register Your Application

Visit the Organization Dashboard to create your OAuth application.

Application Name:Your App Name
Redirect URIs:https://yourapp.com/auth/callback
Scopes:openid profile email
2

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:

PKCE mandatory for all clients
Authorization Code flow only
Exact redirect URI matching
Short-lived tokens (1 hour)
Refresh token rotation
No implicit or password flow
1

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();
2

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_token
3

Get 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_SECRET

2. 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=xyz123

3. 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_SECRET

Response (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

EndpointMethodDescription
/oauth/authorizeGETOAuth authorization endpoint
/oauth/tokenPOSTExchange code for tokens
/api/user/profileGETGet user information
/oauth/revokePOSTRevoke access token
/.well-known/openid-configurationGETOpenID Connect discovery
/.well-known/jwks.jsonGETJSON Web Key Set

OAuth Scopes

ScopeDescriptionClaims Returned
openidRequired for OIDCsub
profileUser profile informationname, picture, given_name, family_name, preferred_username, updated_at
emailUser email addressemail, email_verified
phoneUser phone numberphone_number, phone_number_verified
addressUser addressaddress (structured object)
offline_accessRefresh tokenEnables 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.

App Domain: newsforge.news
Automatically configures:
โ€ข 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:
# DNS Configuration
auth.newsforge.news CNAME newsforge-app.herokuapp.com
api.newsforge.news CNAME newsforge-api.aws.com
# OAuth Redirect URIs
https://auth.newsforge.news/callback โœ…
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 Dashboard

OAuth 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.