ãã¡ãã¯ã¨ã ã¹ãªã¼ Advent Calendar 2024 8æ¥ç®ã®è¨äºã§ãã
AIã»æ©æ¢°å¦ç¿ãã¼ã (以ä¸ãAIãã¼ã )ã®ä¸æä¼å¹(@inakam)ããéããã¾ãã
ç¤¾å æ¨ªæçã«æ©æ¢°å¦ç¿å¨ãããªãã§ããããã¢ããã¼ã«ããAIãã¼ã ã§ãããç¾å¨ç§ã¯ã¯ã©ã¤ã¢ã³ãåãã«èªè¨¼æ©è½ãçµã¿è¾¼ã¾ãããããã¯ããéçºãã¦ãã¾ãã
èªè¨¼è¦ä»¶ã¯æ¬¡ã®ããã«ãªã£ã¦ããããããFirebase Authenticationã§å®ç¾ããæ¹æ³ããã®è¨äºã§ã¯è§£èª¬ãã¾ãã ãªããFirebaseã®å°å ¥ã«ã¤ãã¦ã¯ãã®è¨äºã®ç¯å²å¤ã¨ãã¾ãã*1
- ã¡ã¼ã«ãªã³ã¯ãç¨ãã¦ãã¹ã¯ã¼ãã¬ã¹ã«èªè¨¼ãã
- ã»ãã·ã§ã³æ å ±ãCookieã«ä¿æãã
- ã»ãã·ã§ã³ã¯åæ°¸ç¶çã«ç¶æãã
- ã¤ã¾ãä¸å®æéå ã«ã¢ã¯ã»ã¹ãããã°ãã»ãã·ã§ã³æéãå»¶é·ããã
ã¾ãã³ã¼ãä¾ã¨ãã¦ãããã³ãã¨ã³ãã¯React(Firebase SDK)ããµã¼ãã¼ãµã¤ãã¯Goè¨èª(Firebase Admin SDK)ãä¾ã«è§£èª¬ãã¦ããã¾ãã

- Firebase Authenticationã«ãããã¹ã¯ã¼ãã¬ã¹èªè¨¼
- ã¡ã¼ã«ã¢ãã¬ã¹ã«ãããã¹ã¯ã¼ãã¬ã¹èªè¨¼ãå®è£ ãã
- ã»ãã·ã§ã³Cookieã«ããèªè¨¼ãå®è£ ãã
- ã»ãã·ã§ã³Cookieã宿çã«æ´æ°ãã
- ã¾ã¨ã
Firebase Authenticationã«ãããã¹ã¯ã¼ãã¬ã¹èªè¨¼

Firebase Authenticationã§ã¯ããã°ã¤ã³ç¨ã®èªè¨¼URLãå«ãã ã¡ã¼ã«ãã¦ã¼ã¶ã¼ã«éä¿¡ãããã®èªè¨¼URLãããã¹ã¯ã¼ãã¬ã¹ã§èªè¨¼ã§ããæ©è½ãç¨æããã¦ãã¾ãã Firebaseããã¸ã§ã¯ãã§ã¡ã¼ã«ãªã³ã¯ãã°ã¤ã³ãæå¹ã«ãããã¨ã§å©ç¨ã§ãã¾ãã

ã¡ã¼ã«ã¢ãã¬ã¹ã«ãããã¹ã¯ã¼ãã¬ã¹èªè¨¼ãå®è£ ãã
éä¿¡é¨åã®ä½æ
ã¾ãã¯Firebaseã®SDKãå©ç¨ãã¦ãããã³ãã¨ã³ãã§éä¿¡é¨ã使ãã¾ããä¾ãã°ãã¡ã¼ã«ã¢ãã¬ã¹ãå ¥åãããã©ã¼ã ãç¨æãããã¿ã³ã®onClick()è¦ç´ ã«æ¬¡ã®ãããªé¢æ°ãçµã¿è¾¼ããã¨ã§ãã¡ã¼ã«ãéä¿¡ã§ãã¾ãã
import { getAuth, sendSignInLinkToEmail } from "firebase/auth"; const auth = getAuth(); const [email, setEmail] = useState(""); // ãã©ã¼ã ãªã©ããã»ãããã ... const onClick = async () => { const actionCodeSettings = { // ããã§ã¯ç¾å¨ã®URLãè¨å®ãã¦ããããèªè¨¼URLãå¥ã«è¨å®ãããå ´åã¯ç°ãªããã®ãè¨å®ãã url: window.location.origin, // handleCodeInAppã¯å¸¸ã«trueã§ããå¿ è¦ããã handleCodeInApp: true, }; try { // ã¡ã¼ã«ã¢ãã¬ã¹ã«èªè¨¼URLãå«ãã ã¡ã¼ã«ãéä¿¡ await sendSignInLinkToEmail(auth, email, actionCodeSettings); // èªè¨¼å¾ã«æ£ããæ»ã£ã¦ãã¦ãããã確èªããããã«ãå ¥åãããemailãlocalStorageã«ä¿åãã window.localStorage.setItem("emailForSignIn", email); } catch (error) { alert("èªè¨¼ã¡ã¼ã«ã®éä¿¡ã«å¤±æãã¾ãã"); } };
éä¿¡å¦çãè¡ãã¨ã次ã®ãããªã¡ã¼ã«ãå±ãã¾ãã

注æç¹ã¨ãã¦ããã®èªè¨¼ã¡ã¼ã«ãã³ãã¬ã¼ãã¯ä»æ§ä¸å¤æ´ã§ãã¾ããã*2
ãã夿´ãå¿
è¦ãªå ´åã«ã¯ãgenerateSignInWithEmailLinkã¨ãã颿°ã§Linkã®ã¿ãçæããå¥ã®ã¡ã¼ã«ã·ã¹ãã ã§éä¿¡ããå¿
è¦ãããã¾ãã
èªè¨¼URLãããã°ã¤ã³å¦çãè¡ã
éä¿¡é¨åã宿ããããæ¬¡ã¯èªè¨¼URLããèªè¨¼ãè¡ãã¾ããã¡ã¼ã«ã§èªè¨¼URLãã¯ãªãã¯ããã¨ããªãã¤ã¬ã¯ããç¹°ãè¿ããæçµçã«å
ã®actionCodeSettingsã§è¨å®ããurlã«apiKey oobCode mode langã®ãã©ã¡ã¼ã¿ãä»ä¸ãããç¶æ
ã§è¿ã£ã¦ãã¾ãã
isSignInWithEmailLinkã使ããã¨ã§ãæå®ããURLãèªè¨¼URLã®ãªã³ã¯ããæ¤è¨¼ã§ãã¾ãã*3
ä¾ãã°/loginã¨ããURLãç¨æããå ´åããã®å
ã§å¼ã³åºãã³ã³ãã¼ãã³ãã«æ¬¡ã®å¦çãæ¸ããã¨ã«ãªãã¾ãã
import { getAuth, isSignInWithEmailLink } from "firebase/auth"; const auth = getAuth(); if (isSignInWithEmailLink(auth, window.location.href)) { ... // ç¾å¨ã®URLãèªè¨¼URLã§ããå ´åã®å¦çãããã«æ¸ã }
ããã§ããã«signInWithEmailLinkãå¼ã³åºããã¨ã§ãã¡ã¼ã«ã¢ãã¬ã¹ã¨èªè¨¼URLã«ä»ä¸ããã¦ãããã©ã¡ã¼ã¿ã使ã£ã¦èªè¨¼ã§ãã¾ãã
åç¯ã®ãéä¿¡é¨åã®ä½æãã«ããã¦ãlocalStorageã«å
¥åãããã¡ã¼ã«ã¢ãã¬ã¹ãä¿åãã¦ããã®ã¯ãããã§ç¨ããããã§ãã
signInWithEmailLinkãæåããã¨ãèªè¨¼ãå®äºãããã¨ã«ãªãã¾ãã
import { getAuth, isSignInWithEmailLink } from "firebase/auth"; const auth = getAuth(); if (isSignInWithEmailLink(auth, window.location.href)) { // ç¾å¨ã®URLãèªè¨¼URLã§ããã®ã§ãèªè¨¼å¦çãè¡ã // localStorageããå ¥åãããã¡ã¼ã«ã¢ãã¬ã¹ãåãåºã let email = window.localStorage.getItem('emailForSignIn'); if (!email) { // å ¥åæã¨å¥ã®ãã©ã¦ã¶ã§èªè¨¼URLãéãã¨ãã¡ã¼ã«ã¢ãã¬ã¹ãlocalStorageã«ä¿åããã¦ããªã // ããã§ããã³ãããåºããã¨ã§ãã¡ã¼ã«ã¢ãã¬ã¹ãååº¦å ¥åãã¦ããã email = window.prompt('ãã©ã¼ã ã«å ¥åããã¡ã¼ã«ã¢ãã¬ã¹ãååº¦å ¥åãã¦ãã ãã'); } // ã¡ã¼ã«ã¢ãã¬ã¹ã¨èªè¨¼URLã®çµã¿åããã§èªè¨¼ãã signInWithEmailLink(auth, email, window.location.href) .then((result) => { // localStorageã«ä¿åãã¦ããã¡ã¼ã«ã¢ãã¬ã¹ãåé¤ãã window.localStorage.removeItem('emailForSignIn'); // èªè¨¼ãªã³ã¯ã®ãã©ã¡ã¼ã¿ãæ®ãã®ãé²ã history.replaceState(null, "", "/"); // èªè¨¼ãå®äºããã®ã§ãèªè¨¼å¾ã®ä»»æã®å¦çãè¡ã // getAdditionalUserInfo(result)ãè¡ããã¨ã§ãã¦ã¼ã¶ã¼æ å ±ãåå¾ã§ãã window.location.assign('/profile'); // ä¾ãã°ã©ããã«é·ç§»ããã }) .catch((error) => { // error.codeã§ã¨ã©ã¼ãåãåºãããããå¿ è¦ãããã°å¦çãã }); }
èªè¨¼ç¶æ
ã¯æ°¸ç¶åããããããä¸åº¦ãã°ã¤ã³ãå®äºããå¾ã¯getAuth()ãå¼ã¶ã ãã§ãã°ã¤ã³ãã¦ããã¦ã¼ã¶ã¼ã®æ
å ±ãåå¾ãããã¨ãå¯è½ã§ãã
ãµã¼ãã¼ãµã¤ãã§èªè¨¼æ å ±ãåå¾ãã
ããã¾ã§ã§Firebaseã«ãããåºæ¬çãªèªè¨¼ãè¡ããããã«ãªãã¾ããããããã¾ã§ã®å¦çã¯ããã³ãã¨ã³ãã§å®çµãã¦ãã¾ãã ããã¯ã¨ã³ãã«Web APIãåå¨ãã¦ããå ´åãFirebaseã®èªè¨¼æ å ±ãæ±ãããã«IDTokenã¨å¼ã°ããã¢ã¯ã»ã¹ãã¼ã¯ã³ãAPIã«æ¸¡ãããµã¼ãã¼ãµã¤ãã®Firebase Admin SDKã§å度ã¦ã¼ã¶ã¼æ å ±ãåå¾ãã¾ãã
ããã§ãã¦ã¼ã¶ã¼ã®èªè¨¼æ å ±ãå©ç¨ãããã¨ã¯ã§ãã¾ããããããµã¼ãã¼ãµã¤ãã«å¦çãæããããã«ããããã»ãã·ã§ã³Cookieã«ããèªè¨¼ãå®è£ ãã¾ããã»ãã·ã§ã³Cookieã§ç®¡çãããã¨ã§ãããã³ãã¨ã³ãä¸ã§ã¯ã¨ã³ããã¤ã³ãã¸ã®ã¢ã¯ã»ã¹æã«ã¢ã¯ã»ã¹ãã¼ã¯ã³ãæèããå¿ è¦ããªããªãã¾ãã
ã»ãã·ã§ã³Cookieã«ããèªè¨¼ãå®è£ ãã

ã»ãã·ã§ã³Cookieãçºè¡ããã¨ã³ããã¤ã³ãã使ãã
ã¾ããµã¼ãã¼ãµã¤ãCookie*4ãçºè¡ããããã®ã¨ã³ããã¤ã³ãã使ãã¾ããããã§ã¯ /sessionLoginã«å®è£
ããã¦ããã¨ãã¾ãã
type SessionLoginPayload struct { IDToken string `json:"idToken"` } ... return func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() // ãªã¯ã¨ã¹ãããIDTokenãåå¾ãã var p SessionLoginPayload err := json.NewDecoder(r.Body).Decode(&p) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // IDTokenãæå¹ã§ããããæ¤è¨¼ãã decoded, err := client.VerifyIDToken(r.Context(), idToken) if err != nil { http.Error(w, "Invalid ID token", http.StatusUnauthorized) return } // IDTokenã®çºè¡ãç´è¿5å以å ã§ãããã確èªãã // (IDTokenãçç¨ãããå ´åã«ãæ»æå¯è½æéãæå°éã«ãããã) if time.Now().Unix()-decoded.Claims["auth_time"].(int64) > 5*60 { http.Error(w, "Recent sign-in required", http.StatusUnauthorized) return } // Cookieã®æå¹æéãè¨å®ããã»ãã·ã§ã³Cookieãçºè¡ãã expiresIn := time.Hour * 24 * 5 // 5æ¥é cookie, err := client.SessionCookie(r.Context(), idToken, expiresIn) if err != nil { http.Error(w, "Failed to create a session cookie", http.StatusInternalServerError) return } // ãµã¼ãã¼ãµã¤ãCookieã¨ãã¦ã»ãã http.SetCookie(w, &http.Cookie{ Name: "session", Value: cookie, MaxAge: int(expiresIn.Seconds()), HttpOnly: true, Secure: true, }) w.Write([]byte(`{"status": "success"}`)) }
ã»ãã·ã§ã³Cookieãããã³ãã¨ã³ãã§åãåã
使ãã/sessionLoginã«ããã³ãã¨ã³ãããã¢ã¯ã»ã¹ãã¦ãã»ãã·ã§ã³Cookieãçºè¡ãã¾ãã
auth.setPersistence(inMemoryPersistence);ãå®è¡ããããã³ãã§ã¯Firebase SDKã«ããèªè¨¼æ
å ±ãä¿æããªãããã«ããç¹ã«æ³¨æãã¦ãã ããã
import { getAuth, isSignInWithEmailLink, inMemoryPersistence } from "firebase/auth"; // IDTokenãå«ãã ãªã¯ã¨ã¹ããã»ãã·ã§ã³Cookieçºè¡APIã«éä¿¡ãã const postIdTokenToSessionLogin = async ( idToken: string, ): Promise<Response> => { const res = await fetch([APIã®ãã¹ãå] + "/sessionLogin", { method: "POST", headers: { ContentType: "application/json", }, body: JSON.stringify({ idToken }), }); return res; }; const auth = getAuth(); if (isSignInWithEmailLink(auth, window.location.href)) { // Firebase SDKã§èªè¨¼æ å ±ãæ°¸ç¶åããªãããã«ãã auth.setPersistence(inMemoryPersistence); let email = window.localStorage.getItem('emailForSignIn'); if (!email) { email = window.prompt('ãã©ã¼ã ã«å ¥åããã¡ã¼ã«ã¢ãã¬ã¹ãååº¦å ¥åãã¦ãã ãã'); } signInWithEmailLink(auth, email, window.location.href) .then((result) => { // IDTokenãåå¾ const idToken = result.user.getIdToken(); // IDTokenãã»ãã·ã§ã³Cookieçºè¡APIã«éä¿¡ postIdTokenToSessionLogin(idToken); window.localStorage.removeItem('emailForSignIn'); history.replaceState(null, "", "/"); }) .then(() => { window.location.assign('/profile'); }); }
ã»ãã·ã§ã³Cookieãããã¯ã¨ã³ãã§æ¤è¨¼ãã
ã»ãã·ã§ã³Cookieãçºè¡ã§ããããã¨ã³ããã¤ã³ãããããã§ã»ãã·ã§ã³ãæ¤è¨¼ããå¦çãçµã¿è¾¼ã¿ã¾ããæ¤è¨¼ã«ã¯Firebase Admin SDKã®VerifySessionCookieAndCheckRevokedã使ç¨ãã¾ãã
Goã«ãããå¤ãã®Webãã¬ã¼ã ã¯ã¼ã¯ã§ã¯middlewareããµãã¼ããã¦ãããããmiddlewareã«çµã¿è¾¼ãã§ã¨ã³ããã¤ã³ãã¸ã®ã¢ã¯ã»ã¹æã«æ¤è¨¼ããã¨ç®¡ççã«ã楽ã«ãªãã¨æãã¾ãã
return func(w http.ResponseWriter, r *http.Request) { // ã»ãã·ã§ã³Cookieãåå¾ãã cookie, err := r.Cookie("session") if err != nil { // ã»ãã·ã§ã³Cookieããªãå ´åã¯ãªãã¤ã¬ã¯ããã http.Redirect(w, r, "/login", http.StatusFound) return } // ã»ãã·ã§ã³Cookieãæå¹ã§ããããæ¤è¨¼ãã decoded, err := client.VerifySessionCookieAndCheckRevoked(r.Context(), cookie.Value) if err != nil { // ã»ãã·ã§ã³Cookieãç¡å¹ãªå ´åã¯å度ãã°ã¤ã³ããã http.Redirect(w, r, "/login", http.StatusFound) return } // APIã§ä½ããã®ãã¼ã¿ãè¿ãå¦ç serveContentForUser(w, r, decoded) }
ã»ãã·ã§ã³Cookieã宿çã«æ´æ°ãã
ã»ãã·ã§ã³Cookieãè¨å®ãããã¨ãã§ããããæå¾ã«èªè¨¼ã宿çã«æ´æ°ããä»çµã¿ãä½ãã¾ããã¨ããã®ããå®ã¯Firebase Authenticationã®ã»ãã·ã§ã³Cookieã¯æé·ã§ã2é±éã¾ã§ããèªè¨¼æéãè¨å®ã§ãã¾ããããããã£ã¦ããã®ã¾ã¾ã§ã¯ä¸å®æéãéããã¨å¼·å¶çã«ãã°ã¢ã¦ããã¦ãã¾ãã¾ããå度ã¡ã¼ã«èªè¨¼ã§ãã°ã¤ã³ãã¦ããã£ã¦ãè¯ãã§ããã宿çã«è¨ªããã¦ã¼ã¶ã¼ã«å¯¾ãã¦ã¯èªè¨¼æ å ±ãçãã¦ããã°ãèªè¨¼æ å ±ãæ´æ°ãã¦ãããä»çµã¿ãä½ã£ã¦ã¿ã¾ãã

/sessionLoginã®ä»æ§å¤æ´
ã»ãã·ã§ã³Cookieã®ä½æã¨ã³ããã¤ã³ãã®ä»æ§ã夿´ãã¾ããããã¾ã§ã¯IDTokenã®ã¿ãåãåã£ã¦ãã¾ããããIDTokenã¯1æéã§æéåãã«ãªãã¾ãã1æé以ä¸å¾ã«Cookieãæ´æ°ããããã«ã¯ãªãã¬ãã·ã¥ãã¼ã¯ã³ãå¿ è¦ã«ãªãã¾ããããã§ã»ãã·ã§ã³Cookieçºè¡æã«ãªãã¬ãã·ã¥ãã¼ã¯ã³ãä¿æãã¦ããããã«ãã¾ãã
type SessionLoginPayload struct { IDToken string `json:"idToken"` RefreshToken string `json:"refreshToken"` } ... return func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() // ãªã¯ã¨ã¹ãããIDTokenã¨RefreshTokenãåå¾ãã var p SessionLoginPayload err := json.NewDecoder(r.Body).Decode(&p) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } decoded, err := client.VerifyIDToken(r.Context(), idToken) if err != nil { http.Error(w, "Invalid ID token", http.StatusUnauthorized) return } if time.Now().Unix()-decoded.Claims["auth_time"].(int64) > 5*60 { http.Error(w, "Recent sign-in required", http.StatusUnauthorized) return } expiresIn := time.Hour * 24 * 5 cookie, err := client.SessionCookie(r.Context(), idToken, expiresIn) if err != nil { http.Error(w, "Failed to create a session cookie", http.StatusInternalServerError) return } // ãµã¼ãã¼ãµã¤ãCookieã¨ãã¦ã»ãã http.SetCookie(w, &http.Cookie{ Name: "session", Value: cookie, MaxAge: int(expiresIn.Seconds()), HttpOnly: true, Secure: true, }) // ãªãã¬ãã·ã¥ãã¼ã¯ã³ãä¿æãã¦ãã http.SetCookie(w, &http.Cookie{ Name: "refreshToken", Value: refreshToken, MaxAge: int(expiresIn.Seconds()), HttpOnly: true, Secure: true, Path: "/", }) w.Write([]byte(`{"status": "success"}`)) }
ãªãã¬ãã·ã¥ãã¼ã¯ã³ã«ããã»ãã·ã§ã³Cookieæ´æ°å¦çãå®è£ ãã
ãªãã¬ãã·ã¥ãã¼ã¯ã³ã使ã£ã¦Cookieãçºè¡ããå¦çãå®è£ ãã¾ãããªãã¬ãã·ã¥ãã¼ã¯ã³ããIDTokenãåå¾ããå¦çã¯Firebase Admin SDKã«ã¯å®è£ ããã¦ãã¾ããã*5 ããã§ãã¼ã¯ã³äº¤æç¨ã®REST APIã«ã¢ã¯ã»ã¹ãããã¨ã§ããªãã¬ãã·ã¥ãã¼ã¯ã³ããIDTokenãåå¾ãã¾ããããã¦ããã®IDTokenã使ã£ã¦ã»ãã·ã§ã³Cookieãæ´æ°(åçºè¡)ãããã¨ããå¦çãå®è£ ãã¾ãã
ï¼firebase-config.jsã«æ¸ããã¦ããapiKeyãå¿
è¦ã«ãªãã¾ãï¼
type TokenResponse struct { ExpiresIn string `json:"expires_in"` TokenType string `json:"token_type"` RefreshToken string `json:"refresh_token"` IDToken string `json:"id_token"` UserID string `json:"user_id"` ProjectID string `json:"project_id"` } // https://firebase.google.com/docs/reference/rest/auth?hl=ja#section-refresh-token func fetchIdTokenByRefreshToken(apiKey, refreshToken string) (TokenResponse, error) { // RefreshTokenã使ã£ã¦IDTokenãåå¾ããå¦çã¯Firebase SDKã«ãªããããREST APIã使ã£ã¦å®è£ ãã endpoint := fmt.Sprintf("https://securetoken.googleapis.com/v1/token?key=%s", apiKey) form := url.Values{} form.Add("grant_type", "refresh_token") form.Add("refresh_token", refreshToken) req, err := http.NewRequest("POST", endpoint, strings.NewReader(form.Encode())) if err != nil { return TokenResponse{}, err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") client := &http.Client{} resp, err := client.Do(req) if err != nil { return TokenResponse{}, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return TokenResponse{}, fmt.Errorf("fetchIdTokenByRefreshToken: %s", resp.Status) } var tokenResp TokenResponse err = json.NewDecoder(resp.Body).Decode(&tokenResp) if err != nil { return TokenResponse{}, err } return tokenResp, nil }
return func(w http.ResponseWriter, r *http.Request) { // refreshTokenãCookieããåå¾ refreshTokenCookie, err := r.Cookie("refreshToken") if err != nil { http.Error(w, "internal server error", http.StatusInternalServerError) return } // RefreshTokenã使ã£ã¦IDTokenãåå¾ // firebaseApiKey := os.Getenv("FIREBASE_API_KEY") tokenResp, err := fetchIdTokenByRefreshToken(firebaseApiKey, refreshTokenCookie.Value) if err != nil { http.Error(w, "internal server error", http.StatusInternalServerError) return } // 以ä¸ãIDTokenãåãåã£ã¦ã»ãã·ã§ã³Cookieãçºè¡ããå ´åã¨åãå¦ç decoded, err := client.VerifyIDToken(r.Context(), idToken) if err != nil { http.Error(w, "Invalid ID token", http.StatusUnauthorized) return } if time.Now().Unix()-decoded.Claims["auth_time"].(int64) > 5*60 { http.Error(w, "Recent sign-in required", http.StatusUnauthorized) return } expiresIn := time.Hour * 24 * 5 cookie, err := client.SessionCookie(r.Context(), idToken, expiresIn) if err != nil { http.Error(w, "Failed to create a session cookie", http.StatusInternalServerError) return } // ãµã¼ãã¼ãµã¤ãCookieã¨ãã¦ã»ãã http.SetCookie(w, &http.Cookie{ Name: "session", Value: cookie, MaxAge: int(expiresIn.Seconds()), HttpOnly: true, Secure: true, }) http.SetCookie(w, &http.Cookie{ Name: "refreshToken", Value: refreshTokenCookie.Value, MaxAge: int(expiresIn.Seconds()), HttpOnly: true, Secure: true, Path: "/", }) w.Write([]byte(`{"status": "success"}`))
ãã¨ã¯ããã³ãã¨ã³ããã/refreshSessionã«ã¢ã¯ã»ã¹ãããã¨ã§ãæ¢åã®ã»ãã·ã§ã³Cookieãæ´æ°ã§ãã¾ãã
ã¾ã¨ã
Firebase Authenticationã¯åºã使ããã¦ããã©ã¤ãã©ãªã ã¨æãã¾ãããä»åã®ãããªãã¹ã¯ã¼ãã¬ã¹+ã»ãã·ã§ã³Cookie+åæ°¸ç¶åã¨ããçµã¿åããã§ã¯ãä¸ã®ä¸ã«ç¥è¦ããªãç¶æ ã§ããã ããããããªè¨äºã«ã¯ãªãã¾ãããã¨ã ã¹ãªã¼AIã»æ©æ¢°å¦ç¿ãã¼ã ã¯æ§ã ãªãã¨ã«ææ¦ãã¦ããã®ã ã¨ãããã¨ãæãã¦ããããã°ã¨æãã¾ãã
We are hiring !!
ã¨ã ã¹ãªã¼AIã»æ©æ¢°å¦ç¿ãã¼ã ã§ã¯ãAIã»æ©æ¢°å¦ç¿ã¯ãã¡ãããã©ããªãã¨ã«ã§ãææ¦ããææ¬²ã®ããã¨ã³ã¸ãã¢ãåéãã¦ãã¾ãã æ°åã»ä¸éããããã®æ¡ç¨ã ãã§ãªããã«ã¸ã¥ã¢ã«é¢è«ãã¤ã³ã¿ã¼ã³ã常æåéãã¦ãã¾ãï¼
ã¨ã³ã¸ãã¢æ¡ç¨ãã¼ã¸ã¯ãã¡ã
ã«ã¸ã¥ã¢ã«é¢è«ããæ°è»½ã«ã©ãã
ã¤ã³ã¿ã¼ã³ã常æåéãã¦ãã¾ã
*1:
ウェブサイトで Firebase Authentication を使ってみる
ãªã©ãåèã«å°å ¥ãæ¸ãã§ãããã®ã¨ãã¾ã
*2:ã¡ã¼ã«ã容æã«éããæ©è½ã§ããæ ãã¹ãã å©ç¨é²æ¢ã®ããã«å¤æ´ã§ããªããªã£ã¦ããããã§ã
*3:å é¨å®è£ ã¨ãã¦ã¯ãmode=signInã®ãã©ã¡ã¼ã¿ãã¤ãã¦ããããæ¤è¨¼ãã¦ããããã§ã
*4:ãµã¼ãã¼ãµã¤ãã®ã¿ã§ã¢ã¯ã»ã¹ã§ããCookieãhttpOnlyãªãã·ã§ã³ãä»ä¸ãããã¨ã§Javascriptããã¢ã¯ã»ã¹ã§ããªããªããããXSSã«å¯¾ããèæ§ãããã
*5:issueã¯ç«ã£ã¦ãã¾ããããã¾ãå¿ è¦ã¨æããã¦ããæ©è½ã§ã¯ãªãããã§ãã