Open4
Create T3 App (Next JS) + App Router で Google ログインを設定する

ID Token を取得したい場合は以下を設定する。

ID Token を refresh させる場合は以下を設定する。

auth.ts
import {
getServerSession,
type DefaultSession,
type NextAuthOptions,
type Account,
} from "next-auth";
import { type JWT } from "next-auth/jwt";
import GoogleProvider from "next-auth/providers/google";
import { env } from "~/env.mjs";
/**
* Module augmentation for `next-auth` types. Allows us to add custom properties to the `session`
* object and keep type safety.
*
* @see https://next-auth.js.org/getting-started/typescript#module-augmentation
*/
declare module "next-auth" {
interface Session extends DefaultSession {
user: {
id: string;
// ...other properties
// role: UserRole;
} & DefaultSession["user"];
accessToken?: string;
accessTokenExpires?: string;
refreshToken?: string;
idToken?: string;
error?: string;
}
}
const GOOGLE_AUTHORIZATION_URL =
"https://accounts.google.com/o/oauth2/v2/auth?" +
new URLSearchParams({
prompt: "consent",
access_type: "offline",
response_type: "code",
}).toString();
/**
* Takes a token, and returns a new token with updated
* `accessToken` and `accessTokenExpires`. If an error occurs,
* returns the old token and an error property
*/
async function refreshToken(token: JWT): Promise<JWT> {
try {
const url =
"https://oauth2.googleapis.com/token?" +
new URLSearchParams({
client_id: process.env.GOOGLE_CLIENT_ID ?? "",
client_secret: process.env.GOOGLE_CLIENT_SECRET ?? "",
grant_type: "refresh_token",
refresh_token: (token.refreshToken ?? "") as string,
}).toString();
const response = await fetch(url, {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
method: "POST",
});
const refreshedTokens = await response.json() as Account;
if (!response.ok) {
throw refreshedTokens;
}
return {
...token,
accessToken: refreshedTokens.access_token,
accessTokenExpires: Date.now() + (refreshedTokens.expires_in as number) * 1000,
refreshToken: refreshedTokens.refresh_token ?? token.refreshToken, // Fall back to old refresh token
};
} catch (error) {
console.error(error);
return {
...token,
error: "RefreshAccessTokenError",
};
}
}
/**
* Options for NextAuth.js used to configure adapters, providers, callbacks, etc.
*
* @see https://next-auth.js.org/configuration/options
*/
export const authOptions: NextAuthOptions = {
callbacks: {
jwt: ({ token, user, account }) => {
if (account && user) {
return {
accessToken: account.access_token,
accessTokenExpires: Date.now() + (account.expires_in as number) * 1000,
refreshToken: account.refresh_token,
idToken: account.id_token,
user,
}
}
// Return previous token if the access token has not expired yet
if (Date.now() < (token.accessTokenExpires as number)) {
return token
}
// Access token has expired, try to update it
return refreshToken(token);
},
session: ({ session, token }) => ({
...session,
user: {
...session.user,
id: token.sub,
},
idToken: token.idToken,
}),
},
providers: [
GoogleProvider({
clientId: env.GOOGLE_CLIENT_ID,
clientSecret: env.GOOGLE_CLIENT_SECRET,
authorization: GOOGLE_AUTHORIZATION_URL,
}),
/**
* ...add more providers here.
*
* Most other providers require a bit more work than the Discord provider. For example, the
* GitHub provider requires you to add the `refresh_token_expires_in` field to the Account
* model. Refer to the NextAuth.js docs for the provider you want to use. Example:
*
* @see https://next-auth.js.org/providers/github
*/
],
secret: process.env.NEXTAUTH_SECRET,
};
/**
* Wrapper for `getServerSession` so that you don't need to import the `authOptions` in every file.
*
* @see https://next-auth.js.org/configuration/nextjs
*/
export const getServerAuthSession = () => getServerSession(authOptions);