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:

PluginPurpose
Headless Login for WPGraphQLGraphQL authentication mutations
miniOrange WP OAuth ServerOAuth2 server functionality

Install miniOrange from WordPress.org:

  1. Go to Plugins → Add New
  2. Search for "miniOrange OAuth Server"
  3. Install and activate OAuth Server by miniOrange

2. Create OAuth Application

Go to miniOrange → OAuth Server → Add Application:

Basic Settings

FieldValue
Application NameWPNuxt App (or your app name)
Redirect URIhttps://your-nuxt-app.com/api/auth/oauth/callback
For local development, use http://localhost:3000/api/auth/oauth/callback

OAuth Settings

SettingRecommended Value
Grant TypeAuthorization Code
Token Endpoint AuthClient Secret Post
Access Token Expiry3600 (1 hour)
Refresh Token Expiry604800 (7 days)

JWT Support (Optional)

In miniOrange → OAuth Server → JWT Support:

SettingValueNotes
Enable JWT supportOptionalTokens 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:

EndpointPath
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'
      }
    }
  }
})

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']
}
ScopeData Returned
openidUser ID
profileName, username, avatar
emailEmail 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

FeatureDescription
State ParameterCSRF protection via random state token
Server-side SecretsclientSecret never exposed to browser
httpOnly CookiesTokens stored securely in production
Short-lived TokensAccess 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:

EnvironmentRedirect URI
Localhttp://localhost:3000/api/auth/oauth/callback
Productionhttps://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 Viewer query
  • User profile data is limited to what miniOrange's userinfo endpoint returns
  • For full user data (avatar, roles), use password authentication

User Data Comparison

FieldPassword AuthOAuth Auth (Free)
ID
Email
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)
Copyright © 2026