Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Firebase Authenticationのセッション管理術

Firebase Authenticationのセッション管理術

7/7 PORT Firebase meetup
https://connpass.com/event/285741/
発表させていただいた資料です

SatohJohn

July 07, 2023
Tweet

More Decks by SatohJohn

Other Decks in Programming

Transcript

  1. Firebase Authenticationの
    セッション管理術
    2023/07/07 PORT Firebase meetup
    株式会社スリーシェイク 佐藤慧太

    View Slide

  2. 自己紹介
    ● 株式会社スリーシェイク Sreake事業部
    ● 佐藤慧太@SatohJohn
    ● SREとしてお客様の労苦を減らす仕事
    ● Google Cloudの製品が好き
    ● FirebaseでのWebアプリケーション経験4年ぐらい

    View Slide

  3. 株式会社スリーシェイクについて
    Copyright © 3-shake, Inc. All Rights Reserved.
      
    xOps
    Plattform
    DesignOps
    IaaS
    DevOps / SRE
    RevOps
    (Revenue Ops)
    HR(Engineer Hiring)
    HROps
    Data Engineering
    DataOps
    Security
    DevSecOps
    SecOps
    事業者が抱える
    セキュリティリスクを無くす
    本格的な脆弱性診断を
    無料で手軽に
    セキュリファイ
    Security
    良いエンジニアに良い案件を
    フリーランスエンジニアに
    「今よりいい条件」を
    リランス
    HR(Engineer Hiring)
    あらゆるサービスを連携する
    ハブになる
    クラウド型ETL/データパイプ
    ラインサービスの決定版
    レコナー
    Data Engineering
    日本のSREをリード
    SRE総合支援からセキュリティ
    対策を全方位支援
    スリーク
    SRE
    スリーシェイク = xOps領域のプラットフォーマーへ

    View Slide

  4. Session管理って
    めんどくさいですよね?

    View Slide

  5. 本日伝えたいこと
    ● Webアプリケーションでの
    Firebase Authenticationのセッション管理方法について
    ○ Cookie
    ○ idTokenとService Worker

    View Slide

  6. 本日伝えたいこと
    ● Webアプリケーションでの
    Firebase Authenticationのセッション管理方法について
    ○ Cookie
    ○ idTokenとService Worker
    実装できる(気がする)まで持っていく

    View Slide

  7. 話さないこと
    ● Service workerの詳しい説明
    ○ fetchしかでてきません
    ● Firebase Authenticationの詳しい説明
    ○ Login後のどう管理するかだけです

    View Slide

  8. 超概略的まとめ
    ● idTokenはaccess tokenのようなもので、有効期限がとても短い
    ● idTokenをcookieに変換して利用する
    ○ サーバ側で検証、処理をするイメージ
    ● service workerを使って、idTokenのまま利用する
    ○ フロント側で検証、処理をするイメージ

    View Slide

  9. Cookieの利用

    View Slide

  10. Cookieでの管理のメリット
    ● ブラウザが保存、取り出しをしてくれるため
    フロントで処理を記載する必要がない
    ● 初回アクセス時にサーバ側で処理できる
    ● 古いブラウザでも対応している

    View Slide

  11. Cookieでの管理のデメリット
    ● Cookieの作成の口を用意しないといけない
    ● 色々なアプリで展開している場合、Cookie用の検証用の口を
    サーバサイドで用意しないと行けない
    ● 他Firebase製品をフロントで使う場合や、Password変更など
    組み合わせる際に、セッション管理が別になり、複雑になる

    View Slide

  12. Cookieシーケンス
    Login部分
    ● Firebaseでのログインをした後
    idTokenを取得する
    ● idTokenをもとにCookieを
    サーバで作成してもらう
    ● ログイン状態の2重管理になるた
    め以下を注意する
    ○ Firebase Authentication
    の情報保存をoffにするこ

    ○ Cookieの保存後に
    Firebase Authentication
    でログアウトすること

    View Slide

  13. Cookieシーケンス
    Login部分
    ● Firebaseでのログインをした後
    idTokenを取得する
    ● idTokenをもとにCookieを
    サーバで作成してもらう
    ● ログイン状態の2重管理になるた
    め以下を注意する
    ○ Firebase Authentication
    の情報保存をoffにするこ

    ○ Cookieの保存後に
    Firebase Authentication
    でログアウトすること

    View Slide

  14. 実装(フロント)
    ● ログイン方法は別になんでもよく
    idTokenが取得できれば良い
    ● Cookie取得のリクエストが成功す
    ればCookieが自動的に
    ブラウザに保存される
    import {
    signInWithEmailAndPassword,
    } from "firebase/auth";
    export const loginWithEmailAndPassword = (auth, email, password) => {
    return signInWithEmailAndPassword(auth, email, password)
    .then(async (result) => {
    const idToken = await result.user.getIdToken();
    return login(idToken);
    })
    };
    export const login = (idToken) => {
    return fetch("/auth/authenticate", {
    method: "POST",
    data: {
    id_token: idToken,
    },
    }).then((data) => data.json())
    };

    View Slide

  15. 実装(サーバ)
    ● フロントから送られてきたidToken
    を使ってCookieを
    組み立てる
    ● CookieにHTTP OnlyとSecure属
    性をつけることを忘れない
    def authenticate(request):
    id_token = request.POST.get("id_token")
    if id_token is None or len(id_token) == 0:
    return HttpResponse("id token not found", status=400)
    session_cookie = create_session_cookie(id_token)
    if session_cookie is None:
    return HttpResponse("Failed to create a session cookie", status=401)
    response = HttpResponse()
    response.set_cookie(
    session_cookie["name"],
    value=session_cookie["value"],
    expires=session_cookie["expires"],
    secure=True,
    httponly=True,
    samesite="Lax",
    )
    return response

    View Slide

  16. 実装(サーバ)
    ● idTokenをデコードすると認証した
    時間が取れる
    ● 認証した時間から公式では
    5分以内であることで不正ではな
    いことを担保している
    ● Cookieで使える値は最大14日間
    の有効期限なのでCookieもその
    時点で消えるようにしておく
    from firebase_admin import auth
    def create_session_cookie(id_token):
    decoded_claims = auth.verify_id_token(id_token)
    if time.time() - decoded_claims["auth_time"] < 5 * 60:
    expires_in = datetime.timedelta(days=14)
    expires = datetime.datetime.now() + expires_in
    session_cookie = auth.create_session_cookie(id_token,
    expires_in=expires_in)
    return build_session_cookie(expires, session_cookie)
    return None
    def build_session_cookie(expires, session_cookie):
    return {"expires": expires, "value": session_cookie, "name":
    LOGIN_COOKIE_NAME}

    View Slide

  17. cookieシーケンス
    リクエスト部分
    ● ブラウザがリクエスト時にCookie
    をつける
    ● 検証結果に応じて処理を
    実施する

    View Slide

  18. 実装(サーバ)
    ● Cookieからclaim取得して
    それからUserを取得する
    ● userが取得できない場合は
    403エラーとしても良いし
    未ログインでも使えるように
    してもよい
    ● Pythonの例ではあるが
    FrameworkのMiddleware上で
    実装するのが良い
    class RequiredAuthorizedMiddleware:
    get_response = None
    REQUIRED = "required"
    KEY = "auth"
    REQUIRED_DICT = {KEY: REQUIRED}
    def __init__(self, get_response):
    self.get_response = get_response
    def process_view(self, request, view_func, view_args, view_kwargs):
    claim =
    auth.verify_session_cookie(request.COOKIES.get(LOGIN_COOKIE_NAME))
    if self.authorized(claim, request):
    request.login_user = convert_login_user(claime)
    return None
    return HttpResponse("fobidden", status=403)
    def authorized(self, claim, request):
    if claim is None:
    return False
    return True

    View Slide

  19. idTokenとservice worker

    View Slide

  20. idTokenとservice workerのメリット
    ● 作成の変換コストがcookieより少ない
    ● client、Firebase間でidTokenの更新をするため、サーバ側での期限切れがパ
    ターンが少ない
    ● idToken作成に使うrefresh tokenがcookieよりも生存する期間が長いため
    再度Loginページ表示が少なくて済む
    ● iOS、Androidと同じような処理で、サーバ側で認証ができる

    View Slide

  21. idTokenとservice workerのデメリット
    ● idTokenの期限が1時間なので、作り直しはcookieより多い
    ● 古いブラウザなどでService Workerに対応していない可能性がある
    ● 初回アクセス時にサーバ側で処理しないため、要件とは合わない可能性がある

    View Slide

  22. service worker
    シーケンス
    ● Cookieのときと、ログインまでは
    変わらない
    ● HTMLを受け取りservice worker
    を、インストールする部分が入って
    いる

    View Slide

  23. 実装(フロント)
    ● Firebase Authenticationで
    ログインをするだけ
    import {
    signInWithEmailAndPassword
    } from "firebase/auth";
    export const loginWithEmailAndPassword = (auth, email, password, afterLogin)
    => {
    return signInWithEmailAndPassword(auth, email, password)
    .then(async (result) => {
    return afterLogin();
    })
    };

    View Slide

  24. service worker
    シーケンス
    ● idTokenが取得できない場合は
    Loginページに遷移させる
    ○ サーバで処理する場合も
    検証は必要なので
    2箇所見る必要がある

    View Slide

  25. 実装(フロント)
    ● getIdTokenの際にtokenが取得で
    きるかをみて
    リダイレクトさせる
    ● httpRequestの際にリクエストをプ
    ロキシすることができるので同じ
    ホストの場合
    Headerに追加する
    ● 公式で公開されているコードがあ
    るのでそちらを見ながら
    触ってみると良いかと
    self.addEventListener("fetch", (event: Event) => {
    const fetchEvent = event as FetchEvent;
    const requestProcessor = (idToken: string | null) => {
    let req = fetchEvent.request;
    let processRequestPromise = Promise.resolve();
    if (self.location.origin == getOriginFromUrl(fetchEvent.request.url)) {
    const headers = new Headers();
    for (let entry of req.headers.entries()) {
    headers.append(entry[0], entry[1]);
    }
    headers.append("Authorization", "Bearer " + idToken);
    processRequestPromise = getBodyContent(req).then((body) => {
    req = new Request(req.url, {
    method: req.method,
    headers: headers,
    mode: "same-origin",
    credentials: req.credentials,
    cache: req.cache,
    redirect: req.redirect,
    referrer: req.referrer,
    body: body as string,
    });
    });
    }
    return processRequestPromise.then(() => fetch(req));
    };
    fetchEvent.respondWith(getIdToken().then(requestProcessor));
    });

    View Slide

  26. 実装(サーバ)
    ● headerからidTokenを取得し
    フロントで行えないユーザ別の処
    理を行う事ができる
    ● この形はアプリでも同じ形に
    なるはず
    function getIdToken(req) {
    const authorizationHeader = req.headers.authorization || '';
    const components = authorizationHeader.split(' ');
    return components.length > 1 ? components[1] : '';
    }
    app.get("/something", (req, res) => {
    const idToken = getIdToken(req);
    admin
    .auth()
    .verifyIdToken(idToken)
    .then((decodedClaims) => {
    return res.json(something(decodedClaims))
    })
    .catch((error) => {
    console.log(error);
    res.status(403);
    res.json({ error: "You must be logged in to continue!" });
    });
    });

    View Slide

  27. 実装(フロント)
    ● サーバとの通信とは関係なく
    ログイン中のユーザの情報を
    取得できる
    ● ログイン中のユーザ情報はauthイ
    ンスタンスから取得する
    ○ idTokenから取得できるレ
    ベルではある
    export const getLoginUser = (auth): Promise => {
    return new Promise((resolve, reject) => {
    const subscribe = auth.onAuthStateChanged((user) => {
    subscribe();
    resolve(user);
    });
    });
    };

    View Slide

  28. まとめ
    ● どっちが良いということはなく、サービスの特性に合わせて利用する
    ○ idTokenをcookieに変換して利用する
    ■ 処理をサーバで実施するようなパターン
    ■ MPAのアプリケーションと相性が良い
    ○ service workerを使って、idTokenのまま利用する
    ■ 処理をフロントで実施するようなパターン
    ■ SPAのアプリケーションと相性が良い

    View Slide

  29. 一緒にSREとして働きませんか?
    ● 社会から労苦をなくしましょう
    ○ 気になりましたら会社ホームページやどんなメンバがいるのか を見ていただき
    申し込みしていただければ!
    ○ または、私の方にtwitterとかでメンションなどでご相談していただければ!

    View Slide

  30. おわり

    View Slide