🐙
NextAuth.jsを使って、ORCID・The Open Science Framework・ GakuNin RDMの認証を行う
概要
NextAuth.jsを使って、ORCID・OSF(The Open Science Framework)・ GRDM(GakuNin RDM)の認証を行う方法です。
デモアプリ
ORCID
OSF
GRDM
リポジトリ
ORCID
以下がオプションの記述例です。
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, // 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; // ORCID ID をセッションに追加
return session;
},
async jwt({ token, account }) {
if (account) {
token.accessToken = account.access_token;
token.orcid = account.orcid;
}
return token;
},
},
};
OSF
以下がオプションの記述例です。
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", // 環境変数でスコープを管理
response_type: "code",
redirect_uri: `${process.env.NEXTAUTH_URL}/api/auth/callback/osf`, // 環境変数からリダイレクトURIを構築
},
},
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 のユーザー ID
name: profile.data.attributes.full_name, // attributesの中からfull_nameを取得
email: profile.data.attributes.email, // attributesの中からemailを取得
};
}
},
],
callbacks: {
async session({ session, token }) {
session.accessToken = token.accessToken;
session.refreshToken = token.refreshToken; // リフレッシュトークンをセッションに保存
session.user.id = token.id; // osf ID をセッションに追加
return session;
},
async jwt({ token, account, user }) {
if (account) {
token.accessToken = account.access_token;
token.refreshToken = account.refresh_token; // リフレッシュトークンを保存
}
if (user) {
token.id = user.id; // ユーザー ID をトークンに保存
}
return token;
},
}
};
GRDM
以下がオプションの記述例です。
export const authOptions = {
// debug: true, // next-auth のデバッグモードを有効化
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, // クエリパラメータでclient_idを送信
scope: process.env.OSF_SCOPE || "osf.full_read osf.full_write", // 環境変数でスコープを管理
response_type: "code",
redirect_uri: `${process.env.NEXTAUTH_URL}/api/auth/callback/gakunin`, // 環境変数からリダイレクトURIを構築
},
},
token: {
url: "https://accounts.rdm.nii.ac.jp/oauth2/token",
async request(context) {
const body = new URLSearchParams({
client_id: process.env.GAKUNIN_CLIENT_ID, // 明示的に client_id を追加
client_secret: process.env.GAKUNIN_CLIENT_SECRET,
code: context.params.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 }) {
// トークンからセッションに必要な情報を追加
session.accessToken = token.accessToken;
session.user = {
...session.user,
id: token.id, // トークンのIDをセッションのユーザーに追加
};
return session;
},
async jwt({ token, account, user }) {
if (account) {
token.accessToken = account.access_token;
token.refreshToken = account.refresh_token; // 必要であれば
}
if (user) {
token.id = user.id; // プロファイルからユーザーIDをトークンに保存
}
return token;
},
},
};
まとめ
改善すべき点などがあるかと思いますが、参考になりましたら幸いです。
Discussion