SpotifyAPIを使ってみようと思い、調べたことをメモとして書いていこうと思います。
NextAuthとは
Next.js に認証機能を簡単に実装できるライブラリのことです。
今回私が利用した、SPotifyだけでなくGoogleやGitHubのアカウントを使用したソーシャルログインを組み込むことができます。
Spotifyログイン機能の実装
Spotify for DevelopersのDashboradページでAppの作成をする。
以下の画面になるので、Appの名前と説明を記入してCREATEをクリックします。
すると自分のAppが作成されて使えるようになります。
ClientIDとClient Secretがこれからの実装で必要になります。
実際にコードを書いていく
ディレクトリ構造は以下のようになっています。
root/
├ src/
│ └ pages/
│ └ api/
│ └ auth/
│ └ [...nextauth].js
│ └ auth/
│ └ _signin.tsx
│ └ _app.tsx
│ └ index.tsx
├ .env
.envのコード
.envファイルには公開しない内容を書いていきます。
Client IDとClient Secretは上のSpotify for developersのページで作成したApp内で発行されたものになります。
NEXTAUTH_URLは開発環境のURLを書いておきます。
SPOTIFY_CLIENT_ID=自分の Client Id
SPOTIFY_CLIENT_SECRET=自分のClient secret
NEXTAUTH_URL=http://localhost:3000
src/pages/api/auth/[...nextauth].jsのコード
最終的には以下のようになりました。
ほとんどNextAuthの公式ドキュメントに従って実装しました。
import NextAuth from "next-auth";
import SpotifyProvider from "next-auth/providers/spotify";
//NextAuth Refresh Token Rotationからコピー。
/**
* 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 refreshAccessToken(token) {
try {
const url =
"https://accounts.spotify.com/api/token?" +
new URLSearchParams({
client_id: process.env.SPOTIFY_CLIENT_ID,
client_secret: process.env.SPOTIFY_CLIENT_SECRET,
grant_type: "refresh_token",
refresh_token: token.refreshToken,
});
const response = await fetch(url, {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
method: "POST",
});
const refreshedTokens = await response.json();
if (!response.ok) {
throw refreshedTokens;
}
return {
...token,
accessToken: refreshedTokens.access_token,
accessTokenExpires: Date.now() + refreshedTokens.expires_in * 1000,
refreshToken: refreshedTokens.refresh_token ?? token.refreshToken, // Fall back to old refresh token
};
} catch (error) {
console.log(error);
return {
...token,
error: "RefreshAccessTokenError",
};
}
}
export default NextAuth({
// 1つまたは複数の認証プロバイダを設定する
providers: [
SpotifyProvider({
clientId: process.env.SPOTIFY_CLIENT_ID,
clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
authorization:
"https://accounts.spotify.com/authorize?scope=user-read-email,playlist-read-private,user-read-email,streaming,user-read-private,user-library-read,user-library-modify,user-read-playback-state,user-modify-playback-state,user-read-recently-played,user-follow-read",
}),
// ...他にも認証プロバイダがあればここから追加する。
],
pages: {
signIn: "/auth/signin",
},
//NextAuth Refresh Token Rotationからコピー。
callbacks: {
//JWT(JSON Web Token)
async jwt({ token, user, account }) {
// 最初のサインイン
if (account && user) {
return {
accessToken: account.access_token,
accessTokenExpires: Date.now() + account.expires_in * 1000,
refreshToken: account.refresh_token,
user,
};
}
// アクセストークンの期限が切れていなかったら前のトークンを返す。
if (Date.now() < token.accessTokenExpires) {
return token;
}
// アクセストークンが切れていたら更新する。
return refreshAccessToken(token);
},
async session({ session, token }) {
session.user = token.user;
session.accessToken = token.accessToken;
session.error = token.error;
return session;
},
},
});
わからなかったこと
SpotifyProviderのauthorizationとは
ここで認証のスコープオプションをつけることができます。
ここで共有することを設定した情報のみが共有されるようになります。
例えば、ユーザーがお気に入り登録した曲の情報を取得するかどうかの設定をすることができます。
Callbackとは
コールバックは、あるアクションが実行されたときに何が起こるかを制御するために使用できる非同期関数のことです。
今回のコードでは、アクセストークンのステータスによって実行される処理を書いています。
src/pages/auth/signin.tsxのコード
最終的には以下のようになりました。
import { getProviders, signIn, useSession } from "next-auth/react";
import Head from "next/head";
import Image from "next/image";
import { useRouter } from "next/router";
import { useEffect } from "react";
import { Loader } from "../../components/Loader";
function Signin({ providers }: any) {
// useSessionで session 情報を保持する変数を作成
const { data: session } = useSession();
const router = useRouter();
useEffect(() => {
if (session) {
router.push("/");
}
}, [session]);
if (session) {
<Loader />;
}
return (
<div className="bg-black h-screen flex flex-col items-center pt-40 space-y-8">
<Head>
<title>Login Music-player</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Image
src="/spotifylogo.png"
height={250}
width={600}
objectFit="contain"
className="animate-pulse"
/>
{/* Object.valueでprovidersを配列にする。 */}
{Object.values(providers).map((provider: any) => (
<div key={provider.name}>
<button
onClick={() => signIn(provider.id)}
className="text-white py-4 px-6 rounded-full bg-[#1db954] transition duration-300 ease-out border border-transparent uppercase font-bold text-xs md:text-base tracking-wider hover:scale-105 hover:bg-[#0db146]"
>
Sign in with {provider.name}
</button>
</div>
))}
</div>
);
}
export default Signin;
// getServerSidePropsはクライアントからのアクセス時にサーバ側でデータを取得しpre-Renderingする。
export async function getServerSideProps() {
const providers = await getProviders();
return {
props: { providers },
};
}
わからなかったこと
useSessionとは
useSessionではサインイン情報を取得することができる。
useSessionはdata
とstatus
のvalueを返す。
・data:Session, undefined, nullの3つのどれかになる。
・セッションがまだ取得されていない時は、data
はundefined
になる。
・セッションの取得に失敗した時は、data
はnull
になる。
・セッションの取得に成功した時は、data
はsession
になる。
・data:loading, authenticated, unauthenticated"の3つのどれかのセッション状態にマッピングする列挙型。
getgetServerSidePropsとは
getProvidersとは
getProviders()メソッドは、/api/auth/providers を呼び出し現在サインイン用に設定されている認証プロバイダの一覧を返します。これは、動的なカスタムサインインページを作成している場合に便利です。
src/pages/_app.tsxのコード
Providerの設定をします。
import "../../styles/globals.css";
import type { AppProps } from "next/app";
import { SessionProvider } from "next-auth/react";
function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
return (
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
);
}
export default MyApp;
src/pages/index.tsxのコード
ルートフォルダにアクセスしたユーザーが認証されたユーザーかどうかを調べて認証されていなかったら、認証ページに飛ばすように設定します。
import Head from "next/head";
import { useRouter } from "next/router";
import { useSession } from "next-auth/react";
import { Loader } from "../components/Loader";
export default function Home() {
const router = useRouter();
// useSessionで session 情報と status 状態を保持する変数を作成
const { status } = useSession({
required: true,
onUnauthenticated() {
// 認証されていないのでサインインページに飛ばす。
router.push("/auth/signin");
},
});
if (status === "loading") {
return <Loader />;
}
return (
<div>
<Head>
<title>Music-player</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Dashboard />
</div>
);
}
わからなかったこと
useSessionの使い方
getServerSidePropsとgetInitialPropsを処理する方法では、セッションが有効かどうかをチェックしてサーバーサイドリクエストを行う必要があり、サーバーの負荷が高まります。useSessionを使えば、常に有効なセッションを持つようにクライアントからリクエストを送ることができるようになります。
デフォルトの動作は、ユーザーをサインインページにリダイレクトし、そこから(ログインに成功した後)、元のページに送り返すことができます。
最後に
以上がNextAuthを利用したSpotifyログイン認証の方法です。
調べながら実装したので、間違っているところがあるかもしれません。
プロバイダの記述を変更すれば、他のソーシャルログインも使えるようになるそうです。