Magicを使ってNext.jsアプリにパスワードレス認証を追加する

Magicというサービスを使うことで、簡単にパスワードレス / WebAuthn またはソーシャルログイン機能を実装することができます。

https://magic.link/

今回はNext.jsをベースに作ってみました。

Step1: Magicアカウントを作成する

https://dashboard.magic.link/signup からアカウントを作成します。ここもパスワードレスになっているあたり流石です。

認証メールを確認すると自動でリダイレクトされますので、開いたページはそのままにしておきましょう。

届いたメールの「Log in to Magic」をクリックします。

ログイン操作していた画面の方が自動でダッシュボードに切り替わります。

Step2: APIキーの取得

First Appというアプリが既に作成されていますので、Get Startedをクリックしましょう。

APIキーが2つあります。ReactなどのフロントエンドではPublishable Keyだけを使うようにしましょう。

万が一お漏らししてしまった場合などには、API Keysタブから再生成します。

Step3: Next.jsのアプリを作ってMagicを組み込む

続いてアプリを作っていきます。

個人的にTypeScriptでやりたい派なので、自前のスターターをインポートして作りました。

$ npx create-next-app magic-example  --example "https://github.com/wpkyoto/nextjs-starter-typescript/tree/main"
$ cd magic-example

フロントエンドでは、magic-sdkをインストールします。

$ yarn add  magic-sdk

あとはImportして、クラスをnewしてやるだけです。

import { Magic } from 'magic-sdk'
const client = new Magic('pk_test_xxxxx')

ちなみに、以下のようなContextを作っておくとuseMagicを使えば簡単にクライアントを取り出せて便利だと思います。

const MagicContext = createContext<{
  magicClient?: Magic;
}>({})

const MagicProvider: FC<PropsWithChildren<{
  publishableAPIKey: string;
}>> = ({publishableAPIKey, children}) => {
  const magic = useMemo(() => {
    if (typeof window === 'undefined') return;
    return new Magic(publishableAPIKey)
  }, [publishableAPIKey])
  return (
    <MagicContext.Provider value={{
      magicClient: magic,
    }}>
      {children}
    </MagicContext.Provider>
  )
}

const useMagic = () => useContext(MagicContext)

tips: SSR / SSGではwindowに注意

内部でwindowプロパティを使用しているらしく、Next.jsやGatsbyなどNode.jsでレンダリングするケースだと ReferenceError: window is not defined エラーを踏みます。

もしこれらのフレームワークを使う場合は、windowがundefinedじゃ無い事を確認するようにしましょう。

Step4: ログイン状況の確認

Magic.user.isLoggedInでログイン状況を確認できます。

  const {magicClient} = useMagic()
  const [isLogin, setLoginStatus] = useState(false)
  useEffect(() => {
    if (!magicClient) return;
    (async() => {
      const result = await magicClient.user.isLoggedIn()
      setLoginStatus(result)
    })()
  }, [magicClient, setLoginStatus])

上のケースでは、以下のように判別できます。

  • magicClient がundefinedの場合、まだステータスチェックが終わっていません
  • magicClient がundefinedではなく、isLoginfalseの場合、ユーザーはログインしていない状態です
  • magicClient がundefinedではなく、isLogintrueの場合、ユーザーはログインしている状態です

あとはこれらの条件に応じてルーティングや表示を制御します。

Step5: Eメールでログインまたはアカウント作成

ユーザー作成とログインはどちらも同じメソッドを使います。

以下のサンプルは、ログイン・アカウント作成をハンドルするフックを提供しています。

const useLoginByMagic = () => {
  const [errorMessage, setErrorMessage] = useState<string>(undefined)
  const [email, setEmail] = useState('')
  const [token, setToken] = useState<string>(undefined)
  const {magicClient} = useMagic()
  const handleLogin = useCallback(async() => {
    if (!magicClient) throw new Error('We have to initilize the magic client before use the hook. Or the Client can not use in Server Side process.')
    try {
      if (!email) throw new Error('Email is required')
      setErrorMessage(undefined)
      const magicAuthToken = await magicClient.auth.loginWithMagicLink({
        email
      })
      setToken(magicAuthToken)
    } catch (e) {
      console.error(e)
      setErrorMessage(e.message)
    }
  }, [email, magicClient, setToken, setErrorMessage])
  return {
    email,
    setEmail,
    token,
    errorMessage,
    handleLogin,
  }
}

setEmailをinputタグなどに使い、メールアドレスの入力を受け付けます。そして handleLoginを呼び出してMagicのログイン・アカウント作成処理を実行しています。あとはtokenerrorMessageの値をuseEffectなどで監視して結果をチェックしてやりましょう。

もしtokenundefinedじゃなくなっていれば、ログイン処理は成功したと考えられます。また、errorMessageに値がある場合は、何かしらのエラーが起きていると推察できます。

まとめ:devにとってもユーザーにとっても簡単。ただ安くは無い

Magicを使うことで、開発側も利用者側もかなり手軽で簡単に会員限定コンテンツを作成・アクセスできます。ただし、フリープランが100アクティブユーザーまでで、101〜500ユーザーは月額35USDとAuth0やAWS Cognito User Pools、Firebaseあたりよりは高価です。

この金額がペイできる価格感のアプリを作る予定か、100ユーザーに収まるクローズドなアプリを想定している場合にはMagicを使うのが良さそうかなと感じました。

もし金額が厳しいという場合は、Auth0 やCognitoなどでも頑張ればWebAuthnやパスワードレス認証を実装できますので、DIYしていきましょう。

Comment