Authentication
OAuth Authentication
Set up OAuth2 login with miniOrange WP OAuth Server
OAuth2 authentication redirects users to WordPress to log in. The password never touches your Nuxt app, providing better security for sensitive applications.
How It Works
1. User clicks "Sign in with WordPress"
│
▼
2. Nuxt redirects to WordPress OAuth page
│
▼
3. User enters credentials on WordPress
│
▼
4. WordPress redirects back with authorization code
│
▼
5. Nuxt exchanges code for access token
│
▼
6. User is authenticated
WordPress Setup
1. Install Required Plugins
You need two plugins:
| Plugin | Purpose |
|---|---|
| Headless Login for WPGraphQL | GraphQL authentication mutations |
| miniOrange WP OAuth Server | OAuth2 server functionality |
Install miniOrange from WordPress.org:
- Go to Plugins → Add New
- Search for "miniOrange OAuth Server"
- Install and activate OAuth Server by miniOrange
2. Create OAuth Application
Go to miniOrange → OAuth Server → Add Application:
Basic Settings
| Field | Value |
|---|---|
| Application Name | WPNuxt App (or your app name) |
| Redirect URI | https://your-nuxt-app.com/api/auth/oauth/callback |
For local development, use
http://localhost:3000/api/auth/oauth/callbackOAuth Settings
| Setting | Recommended Value |
|---|---|
| Grant Type | Authorization Code |
| Token Endpoint Auth | Client Secret Post |
| Access Token Expiry | 3600 (1 hour) |
| Refresh Token Expiry | 604800 (7 days) |
JWT Support (Optional)
In miniOrange → OAuth Server → JWT Support:
| Setting | Value | Notes |
|---|---|---|
| Enable JWT support | Optional | Tokens will contain user info directly |
JWT support is optional. Without it, WPNuxt fetches user info from the userinfo endpoint. With JWT enabled, user data is embedded in the token itself.
3. Copy Client Credentials
After saving, you'll see:
- Client ID: Copy this value
- Client Secret: Copy this value (keep it secret!)
4. Note the Endpoints
miniOrange uses these default endpoints:
| Endpoint | Path |
|---|---|
| Authorization | /wp-json/moserver/authorize |
| Token | /wp-json/moserver/token |
| User Info | /wp-json/moserver/resource |
If you have Faust.js plugin installed, disable "Enable public route redirects" in its settings, otherwise it will intercept OAuth redirects.
Nuxt Configuration
nuxt.config.ts
export default defineNuxtConfig({
modules: ['@wpnuxt/core', '@wpnuxt/auth'],
wpNuxt: {
wordpressUrl: 'https://your-wordpress-site.com'
},
wpNuxtAuth: {
providers: {
password: { enabled: true }, // Keep password as fallback
oauth: {
enabled: true,
clientId: 'your-client-id',
clientSecret: 'your-client-secret'
}
}
}
})
Environment Variables (Recommended)
Don't commit secrets to your repository:
.env
WPNUXT_OAUTH_CLIENT_ID=your-client-id
WPNUXT_OAUTH_CLIENT_SECRET=your-client-secret
nuxt.config.ts
export default defineNuxtConfig({
wpNuxtAuth: {
providers: {
oauth: {
enabled: true,
clientId: process.env.WPNUXT_OAUTH_CLIENT_ID,
clientSecret: process.env.WPNUXT_OAUTH_CLIENT_SECRET
}
}
}
})
Custom Endpoints
If your WordPress uses non-default paths:
nuxt.config.ts
wpNuxtAuth: {
providers: {
oauth: {
enabled: true,
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
// Custom endpoints (relative to wordpressUrl)
authorizationEndpoint: '/oauth/authorize',
tokenEndpoint: '/wp-json/moserver/token',
userInfoEndpoint: '/wp-json/moserver/resource',
scopes: ['openid', 'profile', 'email']
}
}
}
Usage
Login Page with Both Methods
pages/login.vue
<script setup>
const {
login,
loginWithOAuth,
isLoading,
error,
hasPasswordAuth,
hasOAuthAuth
} = useWPAuth()
const form = reactive({ username: '', password: '' })
async function onPasswordLogin() {
const result = await login(form)
if (result.success) navigateTo('/profile')
}
function onOAuthLogin() {
loginWithOAuth() // Redirects to WordPress
}
</script>
<template>
<div class="login-page">
<!-- OAuth Button -->
<button
v-if="hasOAuthAuth()"
@click="onOAuthLogin"
class="oauth-button"
>
Sign in with WordPress
</button>
<!-- Divider -->
<div v-if="hasPasswordAuth() && hasOAuthAuth()" class="divider">
<span>or</span>
</div>
<!-- Password Form -->
<form v-if="hasPasswordAuth()" @submit.prevent="onPasswordLogin">
<input v-model="form.username" placeholder="Username" />
<input v-model="form.password" type="password" placeholder="Password" />
<p v-if="error">{{ error }}</p>
<button type="submit" :disabled="isLoading">
Sign in with password
</button>
</form>
</div>
</template>
OAuth-Only Login
If you only want OAuth:
nuxt.config.ts
wpNuxtAuth: {
providers: {
password: { enabled: false },
oauth: {
enabled: true,
clientId: 'your-client-id',
clientSecret: 'your-client-secret'
}
}
}
pages/login.vue
<script setup>
const { loginWithOAuth, isAuthenticated } = useWPAuth()
// Auto-redirect to OAuth if not authenticated
onMounted(() => {
if (!isAuthenticated.value) {
loginWithOAuth()
}
})
</script>
<template>
<div>Redirecting to WordPress...</div>
</template>
Advanced Configuration
Scopes
Control what user information is requested:
oauth: {
scopes: ['openid', 'profile', 'email']
}
| Scope | Data Returned |
|---|---|
openid | User ID |
profile | Name, username, avatar |
email | Email address |
Multiple Environments
nuxt.config.ts
const isDev = process.env.NODE_ENV === 'development'
export default defineNuxtConfig({
wpNuxtAuth: {
providers: {
oauth: {
enabled: true,
clientId: isDev
? process.env.WPNUXT_OAUTH_CLIENT_ID_DEV
: process.env.WPNUXT_OAUTH_CLIENT_ID_PROD,
clientSecret: isDev
? process.env.WPNUXT_OAUTH_CLIENT_SECRET_DEV
: process.env.WPNUXT_OAUTH_CLIENT_SECRET_PROD
}
}
}
})
Security Features
| Feature | Description |
|---|---|
| State Parameter | CSRF protection via random state token |
| Server-side Secrets | clientSecret never exposed to browser |
| httpOnly Cookies | Tokens stored securely in production |
| Short-lived Tokens | Access tokens expire, refresh tokens for renewal |
Troubleshooting
"OAuth error: invalid_client"
- Verify Client ID matches WordPress
- Check Client Secret is correct
- Ensure Redirect URI matches exactly (including trailing slash)
"Invalid state parameter"
- State cookie may have expired (10 min timeout)
- User may have multiple tabs open
- Clear cookies and try again
User info not loading
- Check User Info endpoint is accessible
- Verify scopes include
profile - Check WordPress user has required permissions
Redirect URI mismatch
The Redirect URI in WordPress must match exactly:
| Environment | Redirect URI |
|---|---|
| Local | http://localhost:3000/api/auth/oauth/callback |
| Production | https://your-app.com/api/auth/oauth/callback |
Add multiple Redirect URIs in miniOrange for different environments.
Limitations
OAuth Token Compatibility
The OAuth token from miniOrange is not compatible with WPGraphQL authentication. This means:
- OAuth users cannot fetch data via the GraphQL
Viewerquery - User profile data is limited to what miniOrange's userinfo endpoint returns
- For full user data (avatar, roles), use password authentication
User Data Comparison
| Field | Password Auth | OAuth Auth (Free) |
|---|---|---|
| ID | ✅ | ✅ |
| ✅ | ✅ | |
| Username | ✅ | ✅ |
| Display name | ✅ | ✅ |
| First/Last name | ✅ | ✅ |
| Nickname | ✅ | ✅ |
| Avatar | ✅ | ❌ |
| Roles | ✅ | ❌ |
| Description/Bio | ✅ | ❌ |
miniOrange Free vs Premium
The free version of miniOrange OAuth Server includes:
- Single OAuth application
- Authorization Code grant
- Basic user info endpoint (email, username, display_name, nickname)
Premium features:
- Multiple applications
- Additional grant types
- Custom claim mapping (required for avatar, roles, and other fields)