NextAuthで認証してGitHubのアクセストークンを取得でのハマりポイント

Next.jsで認証するのに何を使おうか・・

せっかく**Next.js使ってるしNextAuthを使ってみよう！**ということで、軽い気持ちで導入したらドハマリしました。

今回GitHubのアクセストークンを取得する認証の仕組みを作りたいなと思いNextAuthを使いました。

GitHubで認証してアクセストークンの取得でハマったポイントを書きます。

## Next.jsでAPIを定義

NextAuthではAPIを使うのでまずはNext.jsのAPI定義方法をさらっと。

Next.jsでは```pages/api```配下にファイルを配置することで、APIのエンドポイントを定義することができます。

https://nextjs.org/docs/api-routes/introduction

```js:pages/api/user.js
export default (req, res) => {
  res.statusCode = 200
  res.setHeader('Content-Type', 'application/json')
  res.end(JSON.stringify({ name: 'John Doe' }))
}
```

## NextAuthでAPIを実装

NextAuthではNext.jsのAPIルーティングの機能を利用してpages/api/[...nextauth].jsを作ることによって認証に必要なAPIを定義することができます。

https://next-auth.js.org/getting-started/example

```js:pages/api/[...nextauth].js

import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'

const options = {
  site: process.env.SITE || 'http://localhost:3000',

  // Configure one or more authentication providers
  providers: [
    Providers.GitHub({
      clientId: process.env.GITHUB_ID,
      clientSecret: process.env.GITHUB_SECRET
    }),
  ],

  // A database is optional, but required to persist accounts in a database
  database: process.env.DATABASE_URL,
}

export default (req, res) => NextAuth(req, res, options)
```

### NextAuthのセッション管理の種類

NextAuthではセッション管理に2パターンあります。

- DB
- JWTトークン

オプションのdatabaseはDBを使う場合に指定が必要ですが、今回はJWTでのトークン発行を使いたいのでoptionsから消してしまって問題ありません。

もしdatabaseの指定がない場合はオプションのsession.jwtが自動的にtrueになり、JWTトークンによるセッション管理になります。

公式のオプションにあるsessionの項目にも記載があります。

```js

// Use JSON Web Tokens for session instead of database sessions.
// This option can be used with or without a database for users/accounts.
// Note: `jwt` is automatically set to `true` if no database is specified.
```

## セッションの取得

NextAuthでセッションを取得するにはuseSession()やgetSession()を使います。

https://next-auth.js.org/getting-started/client#usesession

そしてドキュメントを見るとデータは下記のような感じで返ってくるんだな、と理解したのですがここがひとつハマりポイントでした。

```typescript
{
  user: {
    name: string,
    email: string,
    image: uri
  },
  accessToken: string,
  expires: "YYYY-MM-DDTHH:mm:ss.SSSZ"
}
```

実際にuseSessionを使ってみると返ってくるデータが下記。

```typescript
{
  user: {
    name: string,
    email: string,
    image: uri
  },
  expires: "YYYY-MM-DDTHH:mm:ss.SSSZ"
}
```

一番欲しいはずのaccessTokenがない・・。

### callbacksを使ってaccessTokenを追加

session.jwtがtrueの場合はaccessTokenが含まれません。
そのため最初に紹介したoptionsで下記のようなcallbacksを追加します。

--------------

コメント頂いたので追記
GitHubのトークンをaccessTokenというキーで返却してしまっていますが、セッション管理のトークンとしての役割ではありません。（キー名がわかりにくい・・）

--------------

```js

callbacks: {
  session: async (session, token) => {
    return Promise.resolve({
      ...session,
      accessToken: token.account.accessToken
    })
  }
}
```



callbacks.sessionはセッション情報を取得しようとしたときに呼ばれ、返すデータを変更することできます。

https://next-auth.js.org/configuration/callbacks


デフォルトでは引数のsessionをそのまま返すため、tokenに含まれるaccessTokenをデータへ含めるようにします。
（上記追記の通り）

引数のsessionとtokenはそれぞれの下記のようデータです。

■session

```typescript

interface Session {
  user: {
    name: string
    email: string | null
    image: string
  }
  expires: string
}
```

■token

```typescript
interface Token {
  user: {
    name: string
    email: string | null
    image: string
  }
  account: {
    provider: 'github'
    type: string
    id: number
    refreshToken?: string
    accessToken: string
    accessTokenExpires: string
  }
  iat: number
  expt: number
}
```

## GitHubのscopeを設定

GitHubで欲しい情報に制限がかかっている場合はscopeを事前に設定した上でアクセストークンを取得する必要があります。

NextAuthでGitHub認証のscopeを設定するにはProviders.GitHubにscopeを追加します。

```js

providers: [
  Providers.GitHub({
    clientId: process.env.GITHUB_ID,
    clientSecret: process.env.GITHUB_SECRET,
    scope: 'repo read:org'
  })
],
```

scopeの指定は文字列で複数指定する場合にはスペースをいれて並べることで指定可能です。

### scopeの指定方法に注意

NextAuthのドキュメントでscopeの指定方法を見つけることができず、scopeの設定に苦戦してしまいました・・。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">githubのGraphQLでちゃんとscopeついてるトークンのはずなのにプライベートが取得できない・・なぜ・・</p>&mdash; Koji Murakami (@koojy3) <a href="https://twitter.com/koojy3/status/1280404142469468161?ref_src=twsrc%5Etfw">July 7, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

scopeは文字列で指定するのが正解ですが、配列で指定しても動いているような挙動になっていたんですよね。

```js

scope: ['repo', 'read:org']
```

こんな感じです。
この指定方法では適切にscopeが設定されたトークンを取得できないので注意が必要です。

## まとめ

わかってしまえばNextAuthはとても便利！という印象を受けましたが、ドキュメントのコードサンプルがもうちょっとあると嬉しいです。

（ちゃんとドキュメントを読んで、コードを読めばいいんですけど・・）