Overview
This is a note about using Drupal OAuth with NextAuth.js.
Behavior
Access the app created with Next.js and press the “Sign in” button.

If you are not logged into Drupal, you will be redirected to the login screen.

If you are already logged in, an authorization button is displayed. Click to authorize.

The login information is displayed.

Drupal-Side Setup
Module Installation
Install the following module.
https://www.drupal.org/project/simple_oauth
I installed the latest version available at the time of writing.
composer require 'drupal/simple_oauth:^6.0@beta'
Generating Keys for Token Encryption
Generate a key pair and save them outside the document root for security.
openssl genrsa -out private.key 2048
openssl rsa -in private.key -pubout > public.key
Setting Key Paths
Set the key paths in the admin panel:
/admin/config/people/simple_oauth
If running Drupal on Amazon Lightsail, you may need to change the file owner as follows.
sudo chown daemon:daemon private.key
Clients
Access /admin/config/services/consumer.
The default_consumer is already created, so edit it.
- Enter a value in
New Secret. - Select
Authorization CodeforGrant types. - Enter the following in
Redirect URIs:- http://localhost:3000/api/auth/callback/drupal
- https://oauth.pstmn.io/v1/callback (for testing with Postman)
- Enter a value for
Access token expiration time.


Verifying with Postman
Enter the following settings. Assume Drupal is set up at https://drupal.example.org.
- Authorization URL: https://drupal.example.org/oauth/authorize
- Access Token URL: https://drupal.example.org/oauth/token
- Client ID: The value configured in Drupal
- Client Secret: The value configured in Drupal

When you click “Get New Access Token”, as shown in the behavior section at the beginning, you are redirected to the Drupal screen. After authorization, you are redirected to:
https://oauth.pstmn.io/v1/callback?code=...

Using from Next.js
You can check the source code here:
https://github.com/nakamura196/drupal_oauth_app
There may be room for improvement, but I was able to verify functionality with the following configuration.
export const authOptions = {
// debug: true, // Enable next-auth debug mode
providers: [
{
id: "drupal",
name: "Drupal",
type: "oauth",
clientId: process.env.DRUPAL_CLIENT_ID,
clientSecret: process.env.DRUPAL_CLIENT_SECRET,
authorization: {
url: process.env.DRUPAL_AUTH_URL,
params: {
scope: process.env.DRUPAL_SCOPE,
response_type: "code",
redirect_uri: `${process.env.NEXTAUTH_URL}/api/auth/callback/drupal`, // Build redirect URI from environment variable
},
},
token: {
async request(context) {
const body = new URLSearchParams({
client_id: process.env.DRUPAL_CLIENT_ID, // Explicitly add client_id
client_secret: process.env.DRUPAL_CLIENT_SECRET,
code: context.params.code, // Authorization code
grant_type: "authorization_code",
redirect_uri: `${process.env.NEXTAUTH_URL}/api/auth/callback/drupal`,
});
const res = await fetch(process.env.DRUPAL_TOKEN_URL, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body,
});
const json = await res.json(); // Parse the response body once
if (!res.ok) {
throw new Error(`Token request failed: ${res.statusText}`);
}
return {
tokens: json
}
}
},
profile(profile) {
return {
id: profile.sub, // Use "sub" as the unique user ID
name: profile.name || profile.preferred_username || "Unknown User", // Set name priority
email: profile.email || "No Email Provided", // Fallback when email is not available
image: profile.profile || null, // Use profile URL as image (adjust as needed)
}
},
},
],
callbacks: {
async session({ session, token }) {
// Add necessary information from token to session
session.accessToken = token.accessToken;
session.user.id = token.id;
return session;
},
async jwt({ token, account, user }) {
if (account) {
token.accessToken = account.access_token;
}
if (user) {
token.id = user.id; // Save user ID from profile to token
}
return token;
},
},
};
Summary
I hope this serves as a helpful reference for using Drupal OAuth.