Apollo with Prismaで簡単なJWT認証を作るとこまで写経する

June 22nd, 2019

リアルワールドで使っていくなら結局データの出し入れができないとなので、次はそこをやってみようと思う。

この辺をメインに参考にしていきます。

写経みたいなものなので完全に備忘録。

言葉を整理する

登場人物が一気に増えるので整理しよう。

GraphQL?

クエリランゲージ。欲しいデータ、ジョインしてほしいデータとかを問い合わせ時に全部指定できる。

RESTと対比されがちだけどこっちのほうがめっちゃ柔軟でいい感じ。まだまだ若いのでこれから成長していくんだと思う。

prisma?

バックエンドにMySQLやPostgreSQLなどを選べるGraphQL実装、スキーマ定義をすれば必要なQueryやMutationをざっと吐き出してくれるツールみたいな感じ。

apollo?

GraqhQLのサーバにつないだりリクエストを投げたりできる。

Reactプラグインもあったりしてかなり汎用的に使えるすごいやつ。

世のフロントエンドエンジニアにApollo Clientを布教したい - Qiita

GraphQLサーバを立ち上げる

prisma が入ってるとdocker-composeでばしっと立ち上がる環境と一緒にplaygroundがついてくるので、

docker-compose up -d

でMySQLと一緒にデーモン起動して、

prisma deploy

でスキーマ反映して、(Railsでいうマイグレーションみたいなもの)

package.json に定義されてる dev スクリプトを流せば、

npm run dev

ブラウザでGraphQLが実行できるようになる。

ここまでのまとめ

ちょっとかなりややこしいんだけど、

  1. src/index.js がprismaの定義を見つつapolloのサーバを起動してつないでくれる
  2. prismaは全Query、Mutationを吐き出してくれちゃうので、src/resolver.js に実際に使うクエリ定義を載せておく
  3. なのでパスワードのハッシュ化などは src/resolver.js などで噛ませることができる
  4. つまり読んでほしくないデータはここで塞ぐ

という感じらしい。つまりこいつはBFF相当の立ち位置になって、フロントエンドは完全に分離する設計になるのかなあ。

JSON WEB TOKENを利用したログイン

src/resolver.js でmutationを追加する。

login: async (parent, { username, password }, ctx, info) => {
  const user = await ctx.prisma.user({ username })

  if (!user) {
    throw new Error('Invalid Login')
  }

  const passwordMatch = await bcrypt.compare(password, user.password)

  if (!passwordMatch) {
    throw new Error('Invalid Login')
  }

  const token = jwt.sign(
    {
      id: user.id,
      username: user.email,
    },
    'my-secret-from-env-file-in-prod',
    {
      expiresIn: '30d', // token will expire in 30days
    },
  )
  return {
    token,
    user,
  }
}

ベアラートークンを使った認証とコンテキストへの保持

次はQueryにcurrentUserを追加。

const resolvers = {
  Query: {
    currentUser: (parent, args, { user, prisma }) => {
      // this if statement is our authentication check
      if (!user) {
        throw new Error('Not Authenticated')
      }
      return prisma.user({ id: user.id })
    },
  },
}
// Mutation: ....

(parent, args, { user, prisma }) になっていて、userとprismaを渡せるように変わっている。

合わせて srd/index.js も対応。

// Add this import to top of your file
const jwt = require('jsonwebtoken')

const getUser = token => {
  try {
    if (token) {
      return jwt.verify(token, 'my-secret-from-env-file-in-prod')
    }
    return null
  } catch (err) {
    return null
  }
}

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req }) => {
    const tokenWithBearer = req.headers.authorization || ''
    const token = tokenWithBearer.split(' ')[1]
    const user = getUser(token)

    return {
      user,
      prisma, // the generated prisma client if you are using it
    }
  },
})

ログイン時に帰ってくるトークンをこんな感じで、プレイグラウンド下部にあるヘッダに設定したらちゃんと取れる。

Screen Shot 2019-06-23 at 18.20.16

というところまで来たので、次はインタフェースを作ろう!

参考にさせていただきました

AUTHOR

Daisuke Tsuji
Daisuke Tsuji@dim0627

フリーのWeb Developer。

RubyとかRailsを触ってる時間が多い。コーディングもマークアップもライティングもデザインもSEOもやるタイプ。

だいたいどれもだめ。