Skip to content

Commit 0480802

Browse files
authored
Samples snippets for session cookie APIs (#233)
1 parent 182896a commit 0480802

File tree

1 file changed

+199
-0
lines changed

1 file changed

+199
-0
lines changed

snippets/auth.go

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ package snippets
1717
import (
1818
"context"
1919
"encoding/base64"
20+
"encoding/json"
21+
"io/ioutil"
2022
"log"
23+
"net/http"
24+
"time"
2125

2226
firebase "firebase.google.com/go"
2327
"firebase.google.com/go/auth"
@@ -573,3 +577,198 @@ func importWithoutPassword(ctx context.Context, client *auth.Client) {
573577
}
574578
// [END import_without_password]
575579
}
580+
581+
func loginHandler(client *auth.Client) http.HandlerFunc {
582+
// [START session_login]
583+
return func(w http.ResponseWriter, r *http.Request) {
584+
// Get the ID token sent by the client
585+
defer r.Body.Close()
586+
idToken, err := getIDTokenFromBody(r)
587+
if err != nil {
588+
http.Error(w, err.Error(), http.StatusBadRequest)
589+
return
590+
}
591+
592+
// Set session expiration to 5 days.
593+
expiresIn := time.Hour * 24 * 5
594+
595+
// Create the session cookie. This will also verify the ID token in the process.
596+
// The session cookie will have the same claims as the ID token.
597+
// To only allow session cookie setting on recent sign-in, auth_time in ID token
598+
// can be checked to ensure user was recently signed in before creating a session cookie.
599+
cookie, err := client.SessionCookie(r.Context(), idToken, expiresIn)
600+
if err != nil {
601+
http.Error(w, "Failed to create a session cookie", http.StatusInternalServerError)
602+
return
603+
}
604+
605+
// Set cookie policy for session cookie.
606+
http.SetCookie(w, &http.Cookie{
607+
Name: "session",
608+
Value: cookie,
609+
MaxAge: int(expiresIn.Seconds()),
610+
HttpOnly: true,
611+
Secure: true,
612+
})
613+
w.Write([]byte(`{"status": "success"}`))
614+
}
615+
// [END session_login]
616+
}
617+
618+
func loginWithAuthTimeCheckHandler(client *auth.Client) http.HandlerFunc {
619+
// [START check_auth_time]
620+
return func(w http.ResponseWriter, r *http.Request) {
621+
// Get the ID token sent by the client
622+
defer r.Body.Close()
623+
idToken, err := getIDTokenFromBody(r)
624+
if err != nil {
625+
http.Error(w, err.Error(), http.StatusBadRequest)
626+
return
627+
}
628+
629+
decoded, err := client.VerifyIDToken(r.Context(), idToken)
630+
if err != nil {
631+
http.Error(w, "Invalid ID token", http.StatusUnauthorized)
632+
return
633+
}
634+
// Return error if the sign-in is older than 5 minutes.
635+
if time.Now().Unix()-decoded.Claims["auth_time"].(int64) > 5*60 {
636+
http.Error(w, "Recent sign-in required", http.StatusUnauthorized)
637+
return
638+
}
639+
640+
expiresIn := time.Hour * 24 * 5
641+
cookie, err := client.SessionCookie(r.Context(), idToken, expiresIn)
642+
if err != nil {
643+
http.Error(w, "Failed to create a session cookie", http.StatusInternalServerError)
644+
return
645+
}
646+
http.SetCookie(w, &http.Cookie{
647+
Name: "session",
648+
Value: cookie,
649+
MaxAge: int(expiresIn.Seconds()),
650+
HttpOnly: true,
651+
Secure: true,
652+
})
653+
w.Write([]byte(`{"status": "success"}`))
654+
}
655+
// [END check_auth_time]
656+
}
657+
658+
func userProfileHandler(client *auth.Client) http.HandlerFunc {
659+
serveContentForUser := func(w http.ResponseWriter, r *http.Request, claims *auth.Token) {
660+
log.Println("Serving content")
661+
}
662+
663+
// [START session_verify]
664+
return func(w http.ResponseWriter, r *http.Request) {
665+
// Get the ID token sent by the client
666+
cookie, err := r.Cookie("session")
667+
if err != nil {
668+
// Session cookie is unavailable. Force user to login.
669+
http.Redirect(w, r, "/login", http.StatusFound)
670+
return
671+
}
672+
673+
// Verify the session cookie. In this case an additional check is added to detect
674+
// if the user's Firebase session was revoked, user deleted/disabled, etc.
675+
decoded, err := client.VerifySessionCookieAndCheckRevoked(r.Context(), cookie.Value)
676+
if err != nil {
677+
// Session cookie is invalid. Force user to login.
678+
http.Redirect(w, r, "/login", http.StatusFound)
679+
return
680+
}
681+
682+
serveContentForUser(w, r, decoded)
683+
}
684+
// [END session_verify]
685+
}
686+
687+
func adminUserHandler(client *auth.Client) http.HandlerFunc {
688+
serveContentForAdmin := func(w http.ResponseWriter, r *http.Request, claims *auth.Token) {
689+
log.Println("Serving content")
690+
}
691+
692+
// [START session_verify_with_permission_check]
693+
return func(w http.ResponseWriter, r *http.Request) {
694+
cookie, err := r.Cookie("session")
695+
if err != nil {
696+
// Session cookie is unavailable. Force user to login.
697+
http.Redirect(w, r, "/login", http.StatusFound)
698+
return
699+
}
700+
701+
decoded, err := client.VerifySessionCookieAndCheckRevoked(r.Context(), cookie.Value)
702+
if err != nil {
703+
// Session cookie is invalid. Force user to login.
704+
http.Redirect(w, r, "/login", http.StatusFound)
705+
return
706+
}
707+
708+
// Check custom claims to confirm user is an admin.
709+
if decoded.Claims["admin"] != true {
710+
http.Error(w, "Insufficient permissions", http.StatusUnauthorized)
711+
return
712+
}
713+
714+
serveContentForAdmin(w, r, decoded)
715+
}
716+
// [END session_verify_with_permission_check]
717+
}
718+
719+
func sessionLogoutHandler() http.HandlerFunc {
720+
// [START session_clear]
721+
return func(w http.ResponseWriter, r *http.Request) {
722+
http.SetCookie(w, &http.Cookie{
723+
Name: "session",
724+
Value: "",
725+
MaxAge: 0,
726+
})
727+
http.Redirect(w, r, "/login", http.StatusFound)
728+
}
729+
// [END session_clear]
730+
}
731+
732+
func sessionLogoutHandlerWithRevocation(client *auth.Client) http.HandlerFunc {
733+
// [START session_clear_and_revoke]
734+
return func(w http.ResponseWriter, r *http.Request) {
735+
cookie, err := r.Cookie("session")
736+
if err != nil {
737+
// Session cookie is unavailable. Force user to login.
738+
http.Redirect(w, r, "/login", http.StatusFound)
739+
return
740+
}
741+
742+
decoded, err := client.VerifySessionCookie(r.Context(), cookie.Value)
743+
if err != nil {
744+
// Session cookie is invalid. Force user to login.
745+
http.Redirect(w, r, "/login", http.StatusFound)
746+
return
747+
}
748+
if err := client.RevokeRefreshTokens(r.Context(), decoded.UID); err != nil {
749+
http.Error(w, "Failed to revoke refresh token", http.StatusInternalServerError)
750+
return
751+
}
752+
753+
http.SetCookie(w, &http.Cookie{
754+
Name: "session",
755+
Value: "",
756+
MaxAge: 0,
757+
})
758+
http.Redirect(w, r, "/login", http.StatusFound)
759+
}
760+
// [END session_clear_and_revoke]
761+
}
762+
763+
func getIDTokenFromBody(r *http.Request) (string, error) {
764+
b, err := ioutil.ReadAll(r.Body)
765+
if err != nil {
766+
return "", err
767+
}
768+
769+
var parsedBody struct {
770+
IDToken string `json:"idToken"`
771+
}
772+
err = json.Unmarshal(b, &parsedBody)
773+
return parsedBody.IDToken, err
774+
}

0 commit comments

Comments
 (0)