🤝

Auth0のGo SDKで複数のソーシャルアカウントをひもづける

2024/09/12に公開

株式会社バニッシュ・スタンダードのヒダです。弊社で提供している「STAFF START」というtoBのサービスでGoのバックエンドだったりiOSアプリの開発をやったりしています。

今回はAuth0というサービスのアカウントリンク機能について、Goでどのように使うのかと、その注意点を説明します。Auth0については私の1つ前の記事で説明しているのでそちらをお読みください。

https://zenn.dev/vs_blog/articles/dc5123795b83b8

はじめに

Auth0を使うと、GmailやApple、LINEといったアカウントで自分のサービスにログインしてもらえる仕組みをある程度、かんたんに構築できます。

ただ普通にログイン手段を複数用意するだけだと、同じユーザがGmailとAppleアカウントのそれぞれでサービスに登録した場合、別のユーザとして扱われてしまいます

この時、GmailでもAppleアカウントでもログインしてもらってかつ、どちらも同じユーザとして扱えるようにするにはどうすればいいのでしょうか。そのニーズに応えるのがAuth0の「アカウントリンク」です。

英語しかなさそうですが、公式ドキュメントのUser Account Linking以下にドキュメントがあるので、より詳しく知りたい方はこちらもお読みください。

Auth0のアカウントリンクとは

まず最初に期待を裏切らないようにいうと、アカウントリンクはとてもシンプルな機能です。そのため分かりやすくはあるが、かゆいところに何でも手が届くような機能ではない、というのが現時点の私の認識です。

その上で説明をすると、アカウントリンクはこのような状態の複数のアカウントを

例えば以下のようにアカウント2を親のアカウント(プライマリアカウントと呼びます)として、そのアカウントに他のアカウントをセカンダリアカウントとしてひもづけることができます。

いくつか補足すると、

  • 1つのプライマリアカウントに複数のセカンダリアカウントをひもづけられるが、セカンダリにさらに別のアカウントをひもづけたりはできない。つまりシンプルな親子関係しか存在しない
  • プライマリ/セカンダリにできるアカウントの種類について特に制限はない。メールアドレス/パスワードのアカウントでも、Gmailのアカウントでもプライマリになれる
  • 後からプライマリとセカンダリを入れかえる簡単な方法はない
    • プライマリアカウントを別のアカウントに変更したいといったケースに対応するには基本的にはいったん全部ひもづけを解除してひもづけしなおさないといけない

アカウントリンクをするとどうなるか

このあと、アカウントリンクに関わるAPIを見ていきますが、その前にアカウントリンクを実行した場合、それぞれのアカウントがどうなるかについて説明します。

まず一番分かりやすい変化として、プライマリアカウント以外、つまりセカンダリアカウントには普通の方法ではアクセスできなくなります

例えばAuth0の管理画面の「User Management」「Users」からユーザーの一覧を見ることができますが、別のアカウントにひもづけたアカウント(=どれかのアカウントのセカンダリアカウント)はここに表示されなくなります。ちなみに前回の記事でも触れたように、「Management API」はこのダッシュボードの内容とほとんどイコールなので、APIからもセカンダリアカウントになったアカウントの情報は取得できなくなります。

またプライマリアカウントのユーザー詳細の「Accounts Associated」にはひもづいているセカンダリアカウントの情報が表示されるようになります。例えば以下だとAppleアカウントとLINEアカウントがセカンダリアカウントとしてひもづいていることが分かります。

APIで取得できるユーザ情報も変化します。通常、identitiesの配列の要素は1つで、そこに自身のアカウントの情報が入りますが、別のアカウントをひもづけると2番目以降の要素にひもづけたアカウントの情報が入るようになります。

以下はGmailのアカウントにAppleとLINEのアカウントをひもづけた例です。最初の要素が必ずプライマリアカウントの情報になり、2番目にApple、3番目にLINEのアカウントの情報が入っています。profileDataには各アカウントに特化した情報(例えばLINEならユーザのアイコンのURLなど)が入ります。プライマリアカウントのprofileDataはidentitiesには入っていないですが、その外側に同じような情報があります。

{
    "email": "shunsuke@example.net",
    "name": "俊介",
    /* (略) */
    "identities": [
        {
            "provider": "google-oauth2",
            "user_id": "*****",
            "connection": "google-oauth2",
            "isSocial": true
        },
        {
            "profileData": {
                "email": "s.hida@example.com",
                "name": "Shunsuke",
                /* (略) */
            },
            "user_id": "*****",
            "provider": "apple",
            "connection": "apple",
            "isSocial": true
        },
        {
            "profileData": {
                "email": null,
                "name": "しゅんすけ",
                /* (略) */
            },
            "provider": "line",
            "user_id": "*****",
            "connection": "line",
            "isSocial": true
        }
    ],
    /* (略) */
}

このあとのAPIの説明にもでてきますが、アカウントリンクのAPIはリンク(ひもづけ)とアンリンク(ひもづけ解除)の2つがあり、後者のひもづけ解除をすることで一度セカンダリアカウントになったアカウントも単独のプライマリアカウントに戻せます。そうするとまたユーザ一覧に表示されるなど、全てが元に戻ります。このあたりは一度理解するとすごくシンプルな仕組みかなと思います。

そして最後に一番重要な点として、ひもづけたアカウントでログインした場合にAuth0のユーザIDは何になるのかというと、ある意味当たり前ですがプライマリアカウントのユーザIDになります。上の例だとAppleアカウントでログインしようがLINEアカウントでログインしようが、ユーザIDはGoogleのものになります。

Management APIでのアカウントのひもづけと解除

アカウントのひもづけ

ひもづけにはこのAPIを使います。

Link a User Account

ドキュメントには link_with だけで行う方法と、provider connection_id user_id の3つのパラメータで行う方法の2つの説明があります。 link_with [1]の方はセカンダリアカウントのJWTが必要なので、ReactなどフロントエンドでJWTを取得してそのままそれを使って呼び出す、といった時は便利そうですが、バックエンドだとそれが不要な後者を使った方が簡単だと思います。

3つのパラメータを使う場合のgo-auth0での例は以下のようになります:

// client の生成方法は前回の記事参照
// 例のプライマリアカウントのユーザーIDは `auth0|0123456789`
// セカンダリアカウントのユーザーIDは `google-oauth2|abcde`
uIDs, err := client.User.Link(
    ctx,
    "[プライマリアカウントのユーザーID]",  // 例: auth0|0123456789
    &management.UserIdentityLink{
        UserID:   [セカンダリアカウントのユーザIDのポインタ],  // 例: abcde
        Provider: [セカンダリアカウントのプロバイダのポインタ],  // 例: google-oauth2
    },
)

connection_id を指定していませんが、これが必要なのは例えばAuth0でメールアドレス/パスワードを管理するデータベースを複数使っていて、providerとしては両方 auth0 を指定することになって区別がつかないとき、なので基本的には指定しなくても大丈夫です。

もう1つ注意点としては、プライマリアカウントのユーザーIDは

auth0|0123456789

のように全体を指定しますが、セカンダリの方は上の例でいうと

ユーザIDに 0123456789
プロバイダに auth0

を指定します。 | で元のユーザーIDを分割した文字列をそれぞれ指定しなければいけないことに注意してください。

アカウントのひもづけ解除

ひもづけのAPIが分かれば解除はその逆なだけです。

Unlink a User Identity

uIDs, err := s.Management.User.Unlink(
    ctx,
    "[プライマリアカウントのユーザーID]",
    "[セカンダリアカウントのプロバイダ]",
    "[セカンダリアカウントのユーザーID]",
)

ひもづけ解除の方がパラメータのバリエーションがないのでシンプルなインターフェースになっている、という違いはありますが、セカンダリのアカウントは | でユーザIDを分割した文字列で指定する、というところは同じです。

(おまけ) セカンダリアカウントの情報はどうやって更新するのか

「アカウントリンクをするとどうなるか」でも触れましたが、セカンダリアカウントはAPIからアカウントとしては見えなくなります。ではその情報はどうやって更新すればいいのでしょうか。

答えは通常のユーザ更新APIで connection というパラメータを指定するです。以下が更新APIのドキュメントですが、

Update a User

「BODY PARAMETERS」に「connection」というパラメータがあり、更新したいセカンダリのアカウントのconnectionを指定すればその情報を更新できます。

例えばAuth0のデータベースで管理しているアカウントの場合は "Username-Password-Authentication" を指定します。ここで指定するのはconnectionであってproviderではないので auth0 では駄目、というのがちょっとした罠です。

// 例えばプライマリがGmail、セカンダリがメールアドレス&パスワードの場合にセカンダリのメールアドレスを更新する場合
newEmail := "newemail_of_auth0user@example.com"
connection := "Username-Password-Authentication"
err := client.User.Update(ctx, "google-oauth2|hogehoge", &management.User{
    Email:      &newEmail,
    Connection: &connection,
})

なおセカンダリのアカウントで更新できるのはドキュメントにあるこれらの項目です。

If you are updating email, email_verified, phone_number, phone_verified, username or password of a secondary identity, you need to specify the connection property too.

今回のテーマとは無関係ですが、ユーザの更新時はパスワードとメールアドレスは同時に更新できない、といった制約があるので、その辺にも注意する必要があります(エラーメッセージの一覧を見れば同時に更新できない組み合わせが分かります)。

おわりに

という訳でAuth0のアカウントリンクについて説明しましたが、最初に書いた通り、とてもシンプルなので理解はしやすいと思います。一方でプライマリアカウントの変更や、同じメールアドレスでソーシャルアカウントを登録した時の自動リンクなど、ちょっと特殊なケースになると自分で実装しないといけないので、その辺は把握した上で自分たちのニーズに合わせて使う必要があると思います。

脚注
  1. 今回とりあげない link_with を使ったやり方はフロントエンド(SPA)のサンプルを参考にしてください:
    https://github.com/auth0-samples/auth0-link-accounts-sample/blob/master/SPA/public/js/app.js#L78-L88 ↩︎

Discussion