
Overview
This article describes how to perform authentication with ORCID, OSF (The Open Science Framework), and GRDM (GakuNin RDM) using NextAuth.js.
Demo Apps
ORCID
OSF
GRDM
Repository
ORCID
Below is an example of the options configuration.
https://github.com/nakamura196/orcid_app/blob/main/src/app/api/auth/[...nextauth]/authOptions.js
export const authOptions = {
providers: [
{
id: "orcid",
name: "ORCID",
type: "oauth",
clientId: process.env.ORCID_CLIENT_ID,
clientSecret: process.env.ORCID_CLIENT_SECRET,
authorization: {
url: "https://orcid.org/oauth/authorize",
params: {
scope: "/authenticate",
response_type: "code",
redirect_uri: process.env.NEXTAUTH_URL + "/api/auth/callback/orcid",
},
},
token: "https://orcid.org/oauth/token",
userinfo: {
url: "https://pub.orcid.org/v3.0/[ORCID]",
async request({ tokens }) {
const res = await fetch(`https://pub.orcid.org/v3.0/${tokens.orcid}`, {
headers: {
Authorization: `Bearer ${tokens.access_token}`,
Accept: "application/json",
},
});
return await res.json();
},
},
profile(profile) {
return {
id: profile["orcid-identifier"].path, // Get ORCID ID
name: profile.person?.name?.["given-names"]?.value + " " + profile.person?.name?.["family-name"]?.value,
email: profile.person?.emails?.email?.[0]?.email,
};
},
},
],
callbacks: {
async session({ session, token }) {
session.accessToken = token.accessToken;
session.user.id = token.orcid; // Add ORCID ID to session
return session;
},
async jwt({ token, account }) {
if (account) {
token.accessToken = account.access_token;
token.orcid = account.orcid;
}
return token;
},
},
};
OSF
Below is an example of the options configuration.
https://github.com/nakamura196/osf-app/blob/main/src/app/api/auth/[...nextauth]/authOptions.js
export const authOptions = {
providers: [
{
id: "osf",
name: "Open Science Framework",
type: "oauth",
clientId: process.env.OSF_CLIENT_ID,
clientSecret: process.env.OSF_CLIENT_SECRET,
authorization: {
url: "https://accounts.osf.io/oauth2/authorize",
params: {
scope: process.env.OSF_SCOPE || "osf.full_read osf.full_write", // Manage scope via environment variable
response_type: "code",
redirect_uri: `${process.env.NEXTAUTH_URL}/api/auth/callback/osf`, // Build redirect URI from environment variable
},
},
token: "https://accounts.osf.io/oauth2/token",
userinfo: {
url: "https://api.osf.io/v2/users/me/",
async request({ tokens }) {
const res = await fetch("https://api.osf.io/v2/users/me/", {
headers: {
Authorization: `Bearer ${tokens.access_token}`,
Accept: "application/json",
},
});
return await res.json();
},
},
profile(profile) {
return {
id: profile.data.id, // GakuNin RDM user ID
name: profile.data.attributes.full_name, // Get full_name from attributes
email: profile.data.attributes.email, // Get email from attributes
};
}
},
],
callbacks: {
async session({ session, token }) {
session.accessToken = token.accessToken;
session.refreshToken = token.refreshToken; // Save refresh token to session
session.user.id = token.id; // Add OSF ID to session
return session;
},
async jwt({ token, account, user }) {
if (account) {
token.accessToken = account.access_token;
token.refreshToken = account.refresh_token; // Save refresh token
}
if (user) {
token.id = user.id; // Save user ID to token
}
return token;
},
}
};
GRDM
Below is an example of the options configuration.
https://github.com/nakamura196/rdm_app/blob/main/src/app/api/auth/[...nextauth]/authOptions.js
export const authOptions = {
// debug: true, // Enable next-auth debug mode
providers: [
{
id: "gakunin",
name: "GakuNin RDM",
type: "oauth",
clientId: process.env.GAKUNIN_CLIENT_ID,
clientSecret: process.env.GAKUNIN_CLIENT_SECRET,
authorization: {
url: "https://accounts.rdm.nii.ac.jp/oauth2/authorize",
params: {
client_id: process.env.GAKUNIN_CLIENT_ID, // Send client_id as query parameter
scope: process.env.OSF_SCOPE || "osf.full_read osf.full_write", // Manage scope via environment variable
response_type: "code",
redirect_uri: `${process.env.NEXTAUTH_URL}/api/auth/callback/gakunin`, // Build redirect URI from environment variable
},
},
token: {
url: "https://accounts.rdm.nii.ac.jp/oauth2/token",
async request(context) {
const body = new URLSearchParams({
client_id: process.env.GAKUNIN_CLIENT_ID, // Explicitly add client_id
client_secret: process.env.GAKUNIN_CLIENT_SECRET,
code: context.params.code, // Authorization code
grant_type: "authorization_code",
redirect_uri: `${process.env.NEXTAUTH_URL}/api/auth/callback/gakunin`,
});
const res = await fetch("https://accounts.rdm.nii.ac.jp/oauth2/token", {
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
}
}
},
userinfo: "https://api.rdm.nii.ac.jp/v2/users/me/",
profile(profile) {
if (!profile.data || !profile.data.attributes) {
throw new Error("Invalid user profile structure");
}
const user = {
id: profile.data.id || "unknown", // Handle missing ID gracefully
name: profile.data.attributes.full_name || "No Name",
email: profile.data.attributes.email || "No Email",
};
return user
},
},
],
callbacks: {
async session({ session, token }) {
// Add necessary information from token to session
session.accessToken = token.accessToken;
session.user = {
...session.user,
id: token.id, // Add token ID to session user
};
return session;
},
async jwt({ token, account, user }) {
if (account) {
token.accessToken = account.access_token;
token.refreshToken = account.refresh_token; // If needed
}
if (user) {
token.id = user.id; // Save user ID from profile to token
}
return token;
},
},
};
Summary
There may be areas for improvement, but I hope this serves as a useful reference.



Comments
…