20240721

AWS Cognito Hosted UI

個人開発しているアプリの認証基盤に AWS Cognito を使用するため、土日に弄んでいた。 最初は SDK 経由での実装を試みていたが、ネット上にあまり例が見つからないのと、手順が煩雑な印象を受けていて諦めそうになっていたところ、 YouTube の動画で Hosted UI の機能を知った。 フォーム自体を AWS が生成してくれるのでアプリが acc/pass を全く触ることなく認証を完了できるのがよい。

Hosted UI でコールバックされた後にどうすればいいのかが不明だったが AWS 公式ブログのこの記事の Authorization code grant の通りにやってみたら 無事にユーザープールにアクセスすることができた。Rails アプリとしては UUID で管理されているユーザー ID を Users テーブルに格納するだけでよい。 個人開発者にとって 認証基盤など個人では手に負えない分野は巨大テックの企業努力を利用させてもらう のが正しいアプローチだと思う。

4. After Amazon Cognito verifies the user pool credentials or provider
tokens it receives, the user is redirected to the URL that was specified in
the original redirect_uri query parameter. The redirect also sets a
code query parameter that specifies the authorization code that was
vended to the user by Cognito.

5. The custom application that’s hosted at the redirect URL can then
extract the authorization code from the query parameters and exchange
it for user pool tokens. The exchange occurs by submitting a POST
request to https://AUTH_DOMAIN/oauth2/token with the following
application/x-www-form-urlencoded parameters:

- grant_type – Set to authorization_code for this grant.
- code – The authorization code that’s vended to the user.
- client_id – Same as from the request in step 1.
- redirect_uri – Same as from the request in step 1.

https://aws.amazon.com/blogs/security/how-to-use-oauth-2-0-in-amazon-cognito-learn-about-the-different-oauth-2-0-grants/

curl で 5 の HTTP POST リクエストを作成すると以下のようになる。

➜ curl -X POST https://<MYDOMAIN>.amazoncognito.com/oauth2/token
-H 'Content-Type: application/x-www-form-urlencoded'
-d "grant_type=authorization_code&code=<CODE>&client_id=<CLIENT_ID>
&redirect_uri=http://localhost:3000/auth/callback"

Rails の AuthController#callback の実装コードはベタに書くと以下のようになる。

class AuthController < ApplicationController
    def callback
        code = params[:code]
        uri = URI("https://<MYDOMAIN>.amazoncognito.com/oauth2/token")
        req = Net::HTTP::Post.new(uri)
        req.content_type = 'application/x-www-form-urlencoded'
        req.set_form_data("grant_type": "authorization_code", 
          "code": "#{code}", "client_id": "<CLIENT_ID>", 
          "redirect_uri": "http://localhost:3000/auth/callback")
        res = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => uri.scheme == 'https') do |http|
            http.request(req)
        end
        
        tokens = JSON.parse(res.body)
        client = Aws::CognitoIdentityProvider::Client.new(... snip ...)
        cognito_user = client.get_user({ access_token: tokens["access_token"] })
        cognito_user_id = cognito_user.username # UUID
        
        user = User.where(cognito_user_id: cognito_user_id).first
        if user.nil?
          user = User.create(cognito_user_id: cognito_user_id)
        end
        session[:cognito_user_id] = cognito_user_id
        redirect_to home_path, notice: "Signed up successfully"
    end
end

Firebase Authentication vs AWS Cognito

ネットではよく Firebase Authentication との比較記事があるが、どれも的を得ていないものばかりなのと サンプルコードも Hosted UI を対象にしたものを見つけることができなかった。 記事の内容も浅いものがほとんどで、意図せず shallow end の情報ばかりが過剰供給されているネットの特性を再認識することになった。 少し時間はかかったが、 そのぶん、今回はコピペではなく仕様から理解することにつながったので、結果的にはよかったと思う。 結局、自分の手を動かして理解したり検証する以上に信頼できるのものはない。

Firebase Authentication に不満があるわけではなく、実際に開発環境では長らくこちらを使用していたが、本番環境は AWS におくことを考えているため 認証機能だけ別クラウドになってしまうことにもやもやを抱えていた。今回 AWS Cognito の認証機能の検証が確認できたため、 アカウント管理(AWS Organization + Identity Center) やオブザーバビリティ(Cloud Watch)、 実行基盤(App Runner + RDS) を含めて AWS に集約できる目処が立ったのが長期的にはプラスになってくると思う。

2024-07-21 15:50:49 +0900 +0900


Next
Previous

anime 3
api 1
aws 3
cloudflrare 1
cognito 1
development 1
development process 1
development standard 1
game 1
howto 2
hugo 1
idea 2
impressed 1
leaning toothpick syndrome 1
life 6
log 2
maxim 2
mdx 1
postgresql 2
rails 7
rubocop 1
ruby 1
rust 1
solution 2
stripe 1
tech 17
ui 1
warp 1