LoginSignup
8
3

Next.jsとSlackによる中小企業向け小規模Webアプリケーションの作成

Last updated at Posted at 2023-05-27

Webアプリケーション作成において、ログイン関連機能の実装は重労働です。これらを自前で用意しようとすると、ログイン認証処理、パスワードを忘れた場合の再発行、パスワード変更などが必要となります。

NextAuth.jsとSlackを利用すると、これらの面倒な機能を自前で用意する必要がありません。簡単に実装できるので、中小企業などの小規模アプリケーションにおすすめです。

ローカル環境でHTTPSが利用できるようにする

Slack APIは、リダイレクト先のURLがHTTPSでないと動作しません。local-ssl-proxyが簡単で便利ですので、あらかじめ準備しておいてください。

Slack側の設定

クライアントIDとクライアントシークレットの作成

  1. "Create a new Slack app" ボタンを押します。
  2. "Your Apps" ページで、 "Create New App" ボタンを押して、 "From scratch" を選択します。
  3. アプリケーション名を入力し、アプリケーションを利用するワークスペースを選択します。
  4. "Create App" ボタンを押します。

これで、"Basic Information" ページの "App Credentials" にクライアントIDとクライアントシークレットが表示されます。

リダイレクト先URLの設定

  1. "Basic Information" ページの "Add features and functionality" にある、 "Permissions" をクリックします。
  2. "OAuth & Permissions" ページの "Redirect URLs" にある、 "Add New Redirect URL" ボタンを押します。
  3. https://localhost:3001 を入力して、 "Add" ボタンを押します。
  4. "Save URLs" ボタンを押します。(忘れやすいので注意!)

これで、Slackの認証機能が呼び出せるようになりました。

アプリケーションの作成

とりあえず slack-auth-example というアプリケーションを create-next-app で作ってみます。

npx create-next-app slack-auth-example

必要なパッケージをインストールします(個人的な好みで画面にBootstrap使っています)。

npm install next-auth bootstrap react-bootstrap

環境依存情報を以下のファイルに設定します。

.env.development.local
SLACK_CLIENT_ID = "<Your client ID>"
SLACK_CLIENT_SECRET = "<Your client secret>"
NEXTAUTH_URL = "https://localhost:3001"
NEXTAUTH_SECRET = "changeit"

最低限のログイン認証機能を作ってみます。

/api/auth/[...nextauth].ts
import NextAuth from "next-auth/next"
import SlackProvider from "next-auth/providers/slack"

export const authOptions = {
  providers: [
    SlackProvider({
      clientId: <string>process.env.SLACK_CLIENT_ID,
      clientSecret: <string>process.env.SLACK_CLIENT_SECRET
    })
  ]
}

export default NextAuth(authOptions)

_app.tsxは、<Component><SessionProvider>で囲みます。

/pages/_app.tsx
import '@/styles/globals.css'
import { SessionProvider } from 'next-auth/react'
import type { AppProps } from 'next/app'

export default function App({ Component, pageProps: { session, ...pageProps } }: AppProps) {
  return (
    <SessionProvider session={session}>
      <Component {...pageProps} />
    </SessionProvider>
  )
}

未ログインの場合はログインボタンを、ログイン済みの場合はログインユーザー名とログアウトボタンを表示するページを作ってみます。

/pages/index.tsx
import React from "react"
import { signIn, signOut, useSession } from "next-auth/react"
import { Button } from "react-bootstrap"
import "bootstrap/dist/css/bootstrap.min.css"

export default function Home() {
  const {data: session, status} = useSession()

  if (status === "loading") {
    return <p>Loading...</p>
  }

  if (status === "authenticated") {
    return (
      <>
        <p>You are logged in as {session.user?.name}</p>
        <Button onClick={() => signOut()}>Logout</Button>
      </>
    )
  } else {
    return (
      <Button onClick={() => signIn("slack")}>Login</Button>
    )
  }
}

セッション管理はデータベースでやりたい

NextAuth.jsには、Prisma用のアダプタがあります。これを使えば簡単にデータベースでセッション管理ができるようになります。

インストール

npm install next-auth @prisma/client @next-auth/prisma-adapter
npm install -D prisma

Prismaのセットアップ

今回はMySQLでやってみます。

npx prisma init --datasource-provider mysql

prisma/prisma.schema ファイルが生成されますので、以下のように書き換えます。

prisma/prisma.schema
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

model Account {
  id                 String  @id @default(cuid())
  userId             String
  type               String
  provider           String
  providerAccountId  String
  ok                 Boolean?
  refresh_token      String?  @db.Text
  access_token       String?  @db.Text
  expires_at         Int?
  token_type         String?
  scope              String?
  id_token           String?  @db.Text
  state              String?
  session_state      String?

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
}

model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  userId       String
  expires      DateTime
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model User {
  id            String    @id @default(cuid())
  name          String?
  email         String?   @unique
  emailVerified DateTime?
  image         String?
  accounts      Account[]
  sessions      Session[]
}

model VerificationToken {
  identifier String
  token      String   @unique
  expires    DateTime

  @@unique([identifier, token])
}

生成された.envファイルに以下のような行がありますので、これを自分の環境に合わせて書き換えます。生成されるファイルは.envですが、開発環境と本番環境では別のデータベースサーバーを使うと思いますので、.env.development.localに移しておいた方がよいです。

.env.development.local
DATABASE_URL="mysql://johndoe:randompassword@localhost:3306/mydb"

以下のコマンドでPrismaクライアントを生成します。

npx prisma generate

以下のコマンドでテーブルを作成します。

npx prisma migrate dev --name init

アプリケーションの修正

以下のファイルにadapter:の行を追加します。

/api/auth/[...nextauth].ts
import { PrismaAdapter } from "@next-auth/prisma-adapter"
import { PrismaClient } from "@prisma/client"
import NextAuth from "next-auth/next"
import SlackProvider from "next-auth/providers/slack"

const prisma = new PrismaClient()

export const authOptions = {
  adapter: PrismaAdapter(prisma),
  providers: [
    SlackProvider({
      clientId: <string>process.env.SLACK_CLIENT_ID,
      clientSecret: <string>process.env.SLACK_CLIENT_SECRET
    })
  ]
}

export default NextAuth(authOptions)

2時間程度でいい感じのログイン認証機能ができてしまいました。

補足

「これだと他のドメインでもSlackアカウントさえ持っていたら誰でもログインできてしまうのでは?」と思うかもしれませんが、結論から言うと問題ありません。クライアントIDがSlackドメインに紐づけられているので、そのドメインの利用者しかログインできません。自前でユーザー情報テーブルを用意して、メールアドレスを突合したりというような仕組みは不要です。

8
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3