@@ -17,7 +17,11 @@ package snippets
17
17
import (
18
18
"context"
19
19
"encoding/base64"
20
+ "encoding/json"
21
+ "io/ioutil"
20
22
"log"
23
+ "net/http"
24
+ "time"
21
25
22
26
firebase "firebase.google.com/go"
23
27
"firebase.google.com/go/auth"
@@ -573,3 +577,198 @@ func importWithoutPassword(ctx context.Context, client *auth.Client) {
573
577
}
574
578
// [END import_without_password]
575
579
}
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