ProviderがGitHubの場合はすんなりavatorの画像URLを取得できるのですが、
microsoftアカウントの場合に取得にちょっと苦労しました。
next-authのデフォルト設定のままでProfileのphotoのURLにアクセスしようとするとエラーになります。
{
"error": {
"code": "ErrorInsufficientPermissionsInAccessToken",
"message": "Exception of type 'Microsoft.Fast.Profile.Core.Exception.ProfileAccessDeniedException' was thrown.",
"innerError": {
"date": "2022-12-05T08:03:30",
"request-id": "XXXXXXXX-YYYY-ZZZZZ-a6cf-XXXXXXXXXXXX",
"client-request-id": "XXXXXXXX-YYYY-ZZZZZ-a6cf-XXXXXXXXXXXX"
}
}
}
結論から言うと、以下のscopeの内 User.Readが足りないためにErrorInsufficientPermissionsInAccessTokenが発生します。
authorization: { params: { scope: 'email openid profile User.Read' } },
必ずしもUser.Readじゃなくてもいいのですが、読み取りできるscopeが必要です。
デフォルトの場合、’email openid profile’ が指定されています。
以下、サンプルコードを載せておきます。
async profile(profile, tokens) の部分はAwaitableクラスを返す必要があるため、このコードそのままの場合はtypescript的にはエラーとなります。が、Awaitableはnext-auth内部のクラスっぽく、ここで型の整合性と取るメリットが乏しいため、私はignoreするなどでエラーを無視します。
pages/api/auth/[...nextAuth].ts
import NextAuth, { NextAuthOptions } from 'next-auth';
import AzureADProvider from 'next-auth/providers/azure-ad';
import { PrismaAdapter } from '@next-auth/prisma-adapter';
import { prisma } from '../../../modules/db';
export const authOptions: NextAuthOptions = {
adapter: PrismaAdapter(prisma),
providers: [
AzureADProvider({
clientId: process.env.AZURE_AD_CLIENT_ID,
clientSecret: process.env.AZURE_AD_CLIENT_SECRET,
tenantId: process.env.AZURE_AD_TENANT_ID,
authorization: { params: { scope: 'email openid profile User.Read' } },
async profile(profile, tokens) {
const profileSize = 48;
// https://docs.microsoft.com/en-us/graph/api/profilephoto-get?view=graph-rest-1.0#examples
const profilePicture = await fetch(`https://graph.microsoft.com/v1.0/me/photos/${profileSize}x${profileSize}/$value`, {
headers: {
Authorization: `Bearer ${tokens.access_token}`,
},
});
let image: string | null = null;
if (profilePicture.ok) {
const pictureBuffer = await profilePicture.arrayBuffer();
const pictureBase64 = Buffer.from(pictureBuffer).toString('base64');
image = `data:image/jpeg;base64, ${pictureBase64}`;
}
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image,
};
},
}),
],
callbacks: {
async session({ session, user }) {
session.user.image = user.image ?? '';
return session;
},
},
};
export default NextAuth(authOptions);
next-auth.d.ts
import { DefaultSession } from 'next-auth';
declare module 'next-auth' {
interface Session {
user: {
image: string;
} & DefaultSession['user'];
}
}
型定義ファイルにはuseSessionで戻ったオブジェクトに正確な型情報が欲しいため、上記を追加します。
コメント