
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
https://github.com/nakamura196/orcid_app
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
https://github.com/nakamura196/osf-app
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
https://github.com/nakamura196/rdm_app
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.