Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#153 build an example for firebase-auth #167

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"githubPullRequests.ignoredPullRequestBranches": [
"master"
]
}
Empty file added config/firebase.go
Empty file.
Empty file added firebase-auth/.env
Empty file.
Empty file added firebase-auth/.gitignore
Empty file.
13 changes: 13 additions & 0 deletions firebase-auth/config/cred.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"type": "service_account",
"project_id": "xxxxxxxxx",
"private_key_id": "xxxxxxxxxxxxxxxxxxx",
"private_key": "-----BEGIN PRIVATE KEY-----\xxxxxxxxxxxxxxxxxxxxxxx+\nx/xxxxxxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxxxxxxxxx\n+APMTTNIUbTIQSMhatwsQmSrML9vBdKIr1V+4r8Rv+DZLw72/jsTUMbkcCbR2HoY\nvWWIXLPNmSy6Ulfjh/H2AZV4bPiAeXIbe7KlDGBegGjFGaGvYNw4YaDNZHfzjpDw\nQQzdMXaHGd813PY9lwoz7decdJoY8pUH4MEK5d1x9EYnWcQVlcpqlR8mf4IWHQoH\n4JDmtXmlyE10yqP5Ka3Ygq9doEIAytSQbtRfQRUOzO9a8UqUm/BUeOEtVYmrKBFs\nWwSUFoKlAgMBAAECggEAEM1xZ9bAuQuSXybC92tEieZcFZqHtciHDKj4leVY6PXZ\nyKJNJxXj/8QDhfDkeRIx6LLBVKUjHroc2OfFNSS/nr+TxaHAQK0XP9fQ1MVWvCK4\nglGTCLQ/gJ2N7TbFo8HvI/ZUsno0dikWtkwuNxUC9W6G2gneCtVm093W3zsx/cD6\n4qwGXplYnU8Vgy9fgczGlIMOn3JofuqRgQZRaEJwVO6yTt9+p6+tFrJ1YzsB6Bqm\nOLQ16hh16io96JPCnBbw+ejZpDjBZB6sMMvAwdOh56f1MRjpvpEeIMH220OozQjo\nc+35uxGxGTBRcvlWKlFArjJoOVRCSkVPrdsfq81QAQKBgQD9sxe+ZXmqMnxWXEwP\nIISjYuhSmk624OFpu8myDtoVs2aoTi/IKtdIXRiB0fWknQ+xk290jCymi1SQiDh6\nx4a2N3F6Q8LeYzf3aGt9HLONamcWAnUz0pDSvpt2HKGS1sQNe80PGfY9j9pV8sMM\nQcctuE968wZDZaMov/vPXCEOpQKBgQDvOAGTNwvWmO+UZ/uNwPFBFcFw5Joxpqa8\no4deaWH7gj3Pe0ewEyv02nDN5kBJsMOfGGFILeg5rVpRb6QPK0CRGBabZvx7Ts5Z\nXLf0O4TUvOcmTa2EdgXGqT9CqmXMD2xE24Le820c48gSa0deCFmxY9TVL1DIlazs\nd4eFhblkAQKBgQCILllhd4EObhk2Fkcxm4f3WEFTDceQ/TeilQ3YiYZhPbKuR4H4\nlZ6IFojwLq8IFNL2xCiAzDmvkHztNGH07iOOrkY4liUFUQcaxC9mskBjeakqNFmL\nXs0kgvJaPVYxxxzoC/tvzsTSGOTfW/d9HdX34cawPcGv7d/eIOffUBnijQKBgCVo\nFJb4nJlKAOyr9eIMsa0DwePtW084SApnZ9uTjwbNLu0q0eLunIkFP25y+sqLIYvX\nPB8VTqL2QJFLa9QTZ11I7wAN8p01jX7byMggJn09rMeIjgGQkwaloIbELnbiUoPR\n73z9g6po0/hNXjOOCrh6a+WKKI+1F/1CE6cFaiABAoGAeHx9fY8pccIjStsJvQ1y\nczRXHB20GmKeVle0m3tmiZBoXQXZWAN5n/atq7lbLuTUGuKC+84m1s+c3DFM/urT\nNQiVpz0R7wwvWiwJvKY+2wSI+BDbpwVug5XUsPA6S0fYB7VLinhCRvtj4C0AXOnD\nOMaetbDeKjp3aTdVeVotLEc=\n-----END PRIVATE KEY-----\n",
"client_email": "[email protected]",
"client_id": "xxxxxxxxx",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-5xxxxxxxx%40xxxxxxxxx.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}
37 changes: 37 additions & 0 deletions firebase-auth/config/firebase.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package config

import (
"context"
"fmt"
"log"

"firebase.google.com/go"
"firebase.google.com/go/auth"
"google.golang.org/api/option"
)

const (
ErrorFirebaseInit = "error initializing Firebase app"
ErrorAuthClient = "error getting Auth client"
)
func InitializeFirebase() (*auth.Client, error){
//add cred file downloaded from project settings -> service accounts
opt := option.WithCredentialsFile("/Users/vinayak/Documents/oss/examples/firebase-auth/config/cred.json")

//init new app on firebasea
app, err := firebase.NewApp(context.Background(), nil , opt)
if err != nil {
log.Fatalf(ErrorFirebaseInit+": %v\n",err)
return nil, fmt.Errorf(ErrorFirebaseInit)
}

//init new auth app client
authClient, err := app.Auth(context.Background())
if err != nil {
log.Fatalf(ErrorAuthClient+": %v\n", err)
return nil, err
}

return authClient, nil

}
203 changes: 203 additions & 0 deletions firebase-auth/handlers/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package handlers

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"

"github.com/gin-gonic/examples/firebase-auth/utils"

"firebase.google.com/go/auth"
"github.com/gin-gonic/gin"
)

//turn on auth type on firebase console what ever type you wanna use for auth

type LoginRequest struct {
Token string `json:"token" binding:"required"`
}

type SignupRequest struct {
Email string `json:"email" binding:"required"`
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}


type GoogleSignupRequest struct {
IDToken string `json:"id_token" binding:"required"`
}

//Signup handler
func SignupHandler(firebaseAuth *auth.Client) gin.HandlerFunc {
return func(c *gin.Context) {
var req SignupRequest
if err := c.ShouldBindJSON(&req); err != nil {
utils.RespondWithError(c, http.StatusBadRequest, "Invalid request" )
return
}

existing, _ := firebaseAuth.GetUserByEmail(context.Background(), req.Email)
if existing != nil {
utils.RespondWithError(c, http.StatusNotAcceptable, "User already exisit with this email")
return
}

params := (&auth.UserToCreate{}).
Email(req.Email).
Password(req.Password).
DisplayName(req.Username)


newUser, err := firebaseAuth.CreateUser(context.Background(), params)
if err != nil {
utils.RespondWithError(c, http.StatusInternalServerError, "Failed to create user")
return
}

c.JSON(http.StatusCreated, gin.H{
"message":"User Created",
"uid":newUser.UID,
})
}
}

func SigninWithGoogleHandler(firebaseAuth *auth.Client)gin.HandlerFunc {
return func(c *gin.Context) {
var req GoogleSignupRequest
if err := c.ShouldBindJSON(&req); err != nil {
utils.RespondWithError(c, http.StatusBadRequest, "Invalid Request")
return
}

//verify google
gUser, err := firebaseAuth.VerifyIDToken(context.Background(), req.IDToken)
if err!= nil {
utils.RespondWithError(c, http.StatusBadRequest,"Failed to verify google id token")
return
}

c.Set("user", gUser.UID)
c.JSON(http.StatusAccepted, gin.H{
"message":"Login successful via google",
"uid": gUser.UID,
})

}

}

type EmailLoginRequest struct {
Email string `json:"email" binding:"required"`
Password string `json:"password" binding:"required"`
}

type FirebaseAuthResponse struct {
IdToken string `json:"idToken"`
Error struct {
Message string `json:"message"`
} `json:"error"`
}

// Firebase project-specific endpoint for REST API
const firebaseAuthURL = "https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=YOUR_FIREBASE_WEB_API_KEY"

// EmailLoginHandler handles user login with email and password
func EmailLoginHandler(firebaseAuth *auth.Client) gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("step 0")

var req EmailLoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
utils.RespondWithError(c, http.StatusBadRequest, "Invalid request" )
return
}

fmt.Println("step 1")

_,err := firebaseAuth.GetUserByEmail(c, req.Email)
if err != nil {
utils.RespondWithError(c, http.StatusBadRequest, "Invalid Email" )
return
}

// Create the payload for Firebase REST API
payload := map[string]string{
"email": req.Email,
"password": req.Password,
"returnSecureToken": "true",
}
payloadBytes, _ := json.Marshal(payload)

fmt.Println("step 2")

// Make the request to Firebase REST API
resp, err := http.Post(firebaseAuthURL, "application/json", bytes.NewBuffer(payloadBytes))
if err != nil || resp.StatusCode != http.StatusOK {
utils.RespondWithError(c, http.StatusUnauthorized, "Invalid email or password")
return
}
defer resp.Body.Close()

fmt.Println("step 3")

// Parse the response
body, _ := ioutil.ReadAll(resp.Body)
var authResp FirebaseAuthResponse
if err := json.Unmarshal(body, &authResp); err != nil {
utils.RespondWithError(c, http.StatusInternalServerError, "Failed to parse response from Firebase")
return
}
fmt.Println("step 4")

if authResp.IdToken == "" {
utils.RespondWithError(c, http.StatusUnauthorized, authResp.Error.Message)
return
}
fmt.Println("step 5")

c.JSON(http.StatusOK, gin.H{
"message": "Login successful",
"token": authResp.IdToken,
})
}
}

func LoginHandler(firebaseAuth *auth.Client) gin.HandlerFunc {
return func(ctx *gin.Context) {
var req LoginRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
utils.RespondWithError(ctx, http.StatusBadRequest, "Invalid Request")
return
}

token, err := firebaseAuth.VerifyIDToken(context.Background(), req.Token)
if err != nil {
utils.RespondWithError(ctx, http.StatusUnauthorized, "Invalid Token")
return
}


ctx.JSON(http.StatusAccepted, gin.H{
"message":"Login successfull",
"uid": token.UID,
})

}
}

func ProfileHandler(c *gin.Context){
user, ok := c.Get("user")
if !ok {
c.JSON(http.StatusNotFound, gin.H{
"message":"profile not found",
})
}
c.JSON(http.StatusFound, gin.H{
"message":"profile found",
"user":user,
})
}
34 changes: 34 additions & 0 deletions firebase-auth/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package main

import (
"log"

"github.com/gin-gonic/examples/firebase-auth/config"
"github.com/gin-gonic/examples/firebase-auth/routes"
"github.com/gin-gonic/gin"
)

const (
ErrStartServer = "error starting server"
PORT = "8080"
)
func main(){

//init firebase
firebaseAuth, err := config.InitializeFirebase()
if err != nil {
log.Fatalf(config.ErrorFirebaseInit+": %v",err)
}

//set up router
router := gin.Default()

//register routes
routes.RegisterRoutes(router, firebaseAuth)

//start server
if err := router.Run(":"+PORT); err != nil {
log.Fatalf("err starting server ")
}

}
33 changes: 33 additions & 0 deletions firebase-auth/middlewares/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package middlewares

import (
"context"
"net/http"

"firebase.google.com/go/auth"
"github.com/gin-gonic/examples/firebase-auth/utils"
"github.com/gin-gonic/gin"
)


func AuthMiddleware(firebaseAuth *auth.Client) gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
utils.RespondWithError(c, http.StatusUnauthorized, "Authorization header required")
c.Abort()
return
}

decodeToken, err := firebaseAuth.VerifyIDToken(context.Background(), token)
if err != nil {
utils.RespondWithError(c, http.StatusUnauthorized, "Invalid token")
c.Abort()
return
}

c.Set("user", decodeToken.UID)
c.Next()

}
}
29 changes: 29 additions & 0 deletions firebase-auth/routes/routes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package routes

import (
"firebase.google.com/go/auth"
"github.com/gin-gonic/examples/firebase-auth/handlers"
"github.com/gin-gonic/examples/firebase-auth/middlewares"
"github.com/gin-gonic/gin"
)

//register

func RegisterRoutes(router *gin.Engine, firebaseAuth *auth.Client){

//unprotected
router.POST("/signup", handlers.SignupHandler(firebaseAuth))
router.POST("/login", handlers.LoginHandler(firebaseAuth))
router.POST("/email", handlers.EmailLoginHandler(firebaseAuth))

//using google token(third party)
router.POST("/google", handlers.SigninWithGoogleHandler(firebaseAuth))

//protected routes
protected := router.Group("/protected")
protected.Use(middlewares.AuthMiddleware(firebaseAuth))
{
protected.GET("/profile", handlers.ProfileHandler)
}

}
9 changes: 9 additions & 0 deletions firebase-auth/utils/response.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package utils

import "github.com/gin-gonic/gin"


func RespondWithError(c *gin.Context, code int, message string){
c.JSON(code, gin.H{"error":message})
c.Abort()
}
Loading