Google OAuth Authentication React.js and Golang
This article will teach you how to implement Google OAuth Authentication with React.js, MongoDB-Go-Driver, and Golang.
In addition, you will learn how to return JWT access and refresh token cookies to the user’s browser or client after being authenticated on the server.
Related Articles:
- Google OAuth Authentication React.js and Node.js(No Passport)
- Google OAuth Authentication React.js, MongoDB and Golang
- GitHub OAuth Authentication Vuejs, MongoDB and Golang
- GitHub OAuth Authentication Vue.js and Node.js (No Passport)
- GitHub OAuth Authentication React.js and Node.js(No Passport)
- How to Implement GitHub OAuth in React.js
- How to Implement Google OAuth2 in React.js

Prerequisites
- Have some basic knowledge of HTML, CSS, React.js, MongoDB, and Golang
- HaveGolang installed on your computer
Generate Google client ID and client secret
To begin, search forGoogle OAuth in your browser and click the first link that appears in the search results. You should be taken toOAuth 2.0 to Access Google APIs.
On this page, you will find step-by-step instructions on how to get the access keys. However, I will guide you on creating the client Id and secret the right way.
To retrieve the OAuth 2.0 client credentials, click on theGoogle API Console link.
Note: You will need a Google account to access theGoogle API Console.
Create a New Project
Now, click on theGoogle-Oauth dropdown and a modal should appear where you can choose an existing project or create a new one.
If you haven’t created any project yet, click on the“New Project” button then type the project name and click on the“CREATE” button.

Now, wait for a few seconds for Google to set up the project. Once the project has been successfully created, you will see a success notification.
Next, click on the“SELECT PROJECT” button and tap anywhere on the browser to close the modal.

Create an OAuth Consent Screen
To begin, we first need to create a consent screen before we can generate the credentials. The consent screen is where the user will be redirected when they click on the Sign in with Google OAuth button.
Next, select theOAuth consent screen menu under“APIs & Services” on the left sidebar. On theOAuth consent screen, choose the External User Type if your application is for the public and click on the“CREATE” button.

Next, provide the necessary information for the app consent screen.

After filling the fields, click on the“SAVE AND CONTINUE” button.

Click on the“ADD OR REMOVE SCOPES” button on thescopes page. The number of scopes you can add depends on the type of application you are working on but am only interested in the user’s email and profile information.
After choosing the scopes, scroll down and click on the“UPDATE” button. Next, click on the“SAVE AND CONTINUE” button.

Now, click on theADD USERS button on theTest users tab to add a test user.
Note: The test user is the only account that will have permission to log into your application whilst in sandbox mode.
After adding the test user, click on“SAVE AND CONTINUE”.
On theSummary tab, carefully review your information and click on the“BACK TO DASHBOARD” button once you are convinced the data you provided is correct.
Generate the OAuth Credentials
On the sidebar under“APIs & Services” select Credentials. On theCredentials tab, click on“CREATE CREDENTIALS” and chooseOAuth client ID.

Next, provide the necessary information to allow access to the user’s data. The authorized redirect URI should point to a route on your server. This will enable us to make theGET request directly from the consent screen to the server.
There are different ways you can implement this but I find this method much quicker.

After providing the required data, click on the“CREATE” button and you should get the generated client ID and client secret.
Edit your.env
file in your server folder and add the client Id, client secret, and authorized redirect URI.
.env
GOOGLE_OAUTH_CLIENT_ID=your client Id hereGOOGLE_OAUTH_CLIENT_SECRET=your client secret hereGOOGLE_OAUTH_REDIRECT_URL=http://localhost:8000/api/sessions/oauth/google
In addition, update the.env.local
file in the React.js application.
REACT_APP_GOOGLE_OAUTH_CLIENT_ID=your client Id hereREACT_APP_GOOGLE_OAUTH_CLIENT_SECRET=your client secret hereREACT_APP_GOOGLE_OAUTH_ENDPOINT=http://localhost:8000REACT_APP_GOOGLE_OAUTH_REDIRECT=http://localhost:8000/api/sessions/oauth/google
Generate the Consent Screen URL
After all the configurations above, we are now ready to implement Google OAuth Authentication in our React.js application.
Now we need to define a function to generate the OAuth consent screen URL based on the client ID, the scopes, and the authorized redirect URL.
src/utils/getGoogleUrl.ts
export const getGoogleUrl = (from: string) => { const rootUrl = `https://accounts.google.com/o/oauth2/v2/auth`; const options = { redirect_uri: process.env.REACT_APP_GOOGLE_OAUTH_REDIRECT as string, client_id: process.env.REACT_APP_GOOGLE_OAUTH_CLIENT_ID as string, access_type: 'offline', response_type: 'code', prompt: 'consent', scope: [ 'https://www.googleapis.com/auth/userinfo.profile', 'https://www.googleapis.com/auth/userinfo.email', ].join(' '), state: from, }; const qs = new URLSearchParams(options); return `${rootUrl}?${qs.toString()}`;};
Build the Google OAuth Button with React and MUI
Next, create a login screen with a Google OAuth button havinghref={getGoogleUrl(from)}
.
Let’s create a simple Google OAuth button with React and Material UI.

src/pages/login.page.tsx
import { Box, Container, Typography, Link as MuiLink } from '@mui/material';import { useLocation } from 'react-router-dom';import { ReactComponent as GoogleLogo } from '../assets/google.svg';import { getGoogleUrl } from '../utils/getGoogleUrl';const LoginPage = () => { const location = useLocation(); let from = ((location.state as any)?.from?.pathname as string) || '/'; return ( <Container maxWidth={false} sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh', backgroundColor: '#2363eb', }} > <Box width='27rem'> <Typography variant='h6' component='p' sx={{ my: '1.5rem', textAlign: 'center', color: 'white', }} > Log in with another provider: </Typography> <Box width='100%' sx={{ backgroundColor: '#e5e7eb', p: { xs: '1rem', sm: '2rem' }, borderRadius: 2, }} > <MuiLink href={getGoogleUrl(from)} sx={{ backgroundColor: '#f5f6f7', borderRadius: 1, py: '0.6rem', columnGap: '1rem', textDecoration: 'none', color: '#393e45', cursor: 'pointer', fontWeight: 500, '&:hover': { backgroundColor: '#fff', boxShadow: '0 1px 13px 0 rgb(0 0 0 / 15%)', }, }} display='flex' justifyContent='center' alignItems='center' > <GoogleLogo style={{ height: '2rem' }} /> Google </MuiLink> </Box> </Box> </Container> );};export default LoginPage;
If you came from a previous tutorial in this series, then update thelogin.page.tsx
file to have the Google OAuth button.

src/pages/login.page.tsx
import { Box, Container, Typography, Link as MuiLink } from '@mui/material';import { styled } from '@mui/material/styles';import { FormProvider, SubmitHandler, useForm } from 'react-hook-form';import { object, string, TypeOf } from 'zod';import { zodResolver } from '@hookform/resolvers/zod';import FormInput from '../components/FormInput';import { useEffect } from 'react';import { Link, useLocation, useNavigate } from 'react-router-dom';import { LoadingButton as _LoadingButton } from '@mui/lab';import { toast } from 'react-toastify';import { useLoginUserMutation } from '../redux/api/authApi';import { ReactComponent as GoogleLogo } from '../assets/google.svg';import { getGoogleUrl } from '../utils/getGoogleUrl';const LoadingButton = styled(_LoadingButton)` padding: 0.6rem 0; background-color: #f9d13e; color: #2363eb; font-weight: 500; &:hover { background-color: #ebc22c; transform: translateY(-2px); }`;const LinkItem = styled(Link)` text-decoration: none; color: #2363eb; &:hover { text-decoration: underline; }`;const loginSchema = object({ email: string() .nonempty('Email address is required') .email('Email Address is invalid'), password: string() .nonempty('Password is required') .min(8, 'Password must be more than 8 characters') .max(32, 'Password must be less than 32 characters'),});export type LoginInput = TypeOf<typeof loginSchema>;const LoginPage = () => { const methods = useForm<LoginInput>({ resolver: zodResolver(loginSchema), }); // ? API Login Mutation const [loginUser, { isLoading, isError, error, isSuccess }] = useLoginUserMutation(); const navigate = useNavigate(); const location = useLocation(); const from = ((location.state as any)?.from.pathname as string) || '/profile'; const { reset, handleSubmit, formState: { isSubmitSuccessful }, } = methods; useEffect(() => { if (isSuccess) { toast.success('You successfully logged in'); navigate(from); } if (isError) { if (Array.isArray((error as any).data.error)) { (error as any).data.error.forEach((el: any) => toast.error(el.message, { position: 'top-right', }) ); } else { toast.error((error as any).data.message, { position: 'top-right', }); } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isLoading]); useEffect(() => { if (isSubmitSuccessful) { reset(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isSubmitSuccessful]); const onSubmitHandler: SubmitHandler<LoginInput> = (values) => { // ? Executing the loginUser Mutation loginUser(values); }; return ( <Container maxWidth={false} sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh', backgroundColor: '#2363eb', }} > <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', flexDirection: 'column', }} > <Typography textAlign='center' component='h1' sx={{ color: '#f9d13e', fontWeight: 600, fontSize: { xs: '2rem', md: '3rem' }, mb: 2, letterSpacing: 1, }} > Welcome Back! </Typography> <Typography variant='body1' component='h2' sx={{ color: '#e5e7eb', mb: 2 }} > Login to have access! </Typography> <FormProvider {...methods}> <Box component='form' onSubmit={handleSubmit(onSubmitHandler)} noValidate autoComplete='off' maxWidth='27rem' width='100%' sx={{ backgroundColor: '#e5e7eb', p: { xs: '1rem', sm: '2rem' }, borderRadius: 2, }} > <FormInput name='email' label='Email Address' type='email' /> <FormInput name='password' label='Password' type='password' /> <Typography sx={{ fontSize: '0.9rem', mb: '1rem', textAlign: 'right' }} > <LinkItem to='/forgotpassword' style={{ color: '#333' }}> Forgot Password? </LinkItem> </Typography> <LoadingButton variant='contained' sx={{ mt: 1 }} fullWidth disableElevation type='submit' loading={isLoading} > Login </LoadingButton> <Typography sx={{ fontSize: '0.9rem', mt: '1rem' }}> Need an account? <LinkItem to='/register'>Sign Up Here</LinkItem> </Typography> </Box> </FormProvider> <Typography variant='h6' component='p' sx={{ my: '1.5rem', textAlign: 'center', color: 'white', }} > Log in with another provider: </Typography> <Box maxWidth='27rem' width='100%' sx={{ backgroundColor: '#e5e7eb', p: { xs: '1rem', sm: '2rem' }, borderRadius: 2, }} > <MuiLink href={getGoogleUrl(from)} sx={{ backgroundColor: '#f5f6f7', borderRadius: 1, py: '0.6rem', columnGap: '1rem', textDecoration: 'none', color: '#393e45', cursor: 'pointer', fontWeight: 500, '&:hover': { backgroundColor: '#fff', boxShadow: '0 1px 13px 0 rgb(0 0 0 / 15%)', }, }} display='flex' justifyContent='center' alignItems='center' > <GoogleLogo style={{ height: '2rem' }} /> Google </MuiLink> </Box> </Box> </Container> );};export default LoginPage;
Click on the Google OAuth button and you should be taken to the consent screen where you’ll see your logged-in Google accounts.
On the consent screen, click on the test user account or sign in with the test account if you haven’t already.

The server should return a404 error only if it is running. The most exciting part of the authorized redirect URI is the code in the query string.
The server returned the404 error because we haven’t added the Google OAuth logic yet.

Implement the Google OAuth in Golang and MongoDB
Update theconfig/default.go
file to have the OAuth client Id, the React origin URL, the client secret, and the authorized redirect URL.
Adding the environment variables to the Config struct will enableViper to load and make them available in the project.
go get github.com/spf13/viper
config/default.go
type Config struct {DBUri string `mapstructure:"MONGODB_LOCAL_URI"`RedisUri string `mapstructure:"REDIS_URL"`Port string `mapstructure:"PORT"`ClientOrigin string `mapstructure:"CLIENT_ORIGIN"`AccessTokenPrivateKey string `mapstructure:"ACCESS_TOKEN_PRIVATE_KEY"`AccessTokenPublicKey string `mapstructure:"ACCESS_TOKEN_PUBLIC_KEY"`RefreshTokenPrivateKey string `mapstructure:"REFRESH_TOKEN_PRIVATE_KEY"`RefreshTokenPublicKey string `mapstructure:"REFRESH_TOKEN_PUBLIC_KEY"`AccessTokenExpiresIn time.Duration `mapstructure:"ACCESS_TOKEN_EXPIRED_IN"`RefreshTokenExpiresIn time.Duration `mapstructure:"REFRESH_TOKEN_EXPIRED_IN"`AccessTokenMaxAge int `mapstructure:"ACCESS_TOKEN_MAXAGE"`RefreshTokenMaxAge int `mapstructure:"REFRESH_TOKEN_MAXAGE"`GoogleClientID string `mapstructure:"GOOGLE_OAUTH_CLIENT_ID"`GoogleClientSecret string `mapstructure:"GOOGLE_OAUTH_CLIENT_SECRET"`GoogleOAuthRedirectUrl string `mapstructure:"GOOGLE_OAUTH_REDIRECT_URL"`}func LoadConfig(path string) (config Config, err error) {viper.AddConfigPath(path)viper.SetConfigType("env")viper.SetConfigName("app")viper.AutomaticEnv()err = viper.ReadInConfig()if err != nil {return}err = viper.Unmarshal(&config)return}
Retrieve the Google OAuth Access Token and User’s Profile
Now create autils/googleOAuth.go
file and add these two functions:
GetGoogleOauthToken()
– To get the OAuth Access Token from the Google APIGetGoogleUser()
– Retrieves the user’s profile data with the OAuth Access Token
utils/googleOAuth.go
type GoogleOauthToken struct {Access_token stringId_token string}type GoogleUserResult struct {Id stringEmail stringVerified_email boolName stringGiven_name stringFamily_name stringPicture stringLocale string}func GetGoogleOauthToken(code string) (*GoogleOauthToken, error) {const rootURl = "https://oauth2.googleapis.com/token"config, _ := config.LoadConfig(".")values := url.Values{}values.Add("grant_type", "authorization_code")values.Add("code", code)values.Add("client_id", config.GoogleClientID)values.Add("client_secret", config.GoogleClientSecret)values.Add("redirect_uri", config.GoogleOAuthRedirectUrl)query := values.Encode()req, err := http.NewRequest("POST", rootURl, bytes.NewBufferString(query))if err != nil {return nil, err}req.Header.Set("Content-Type", "application/x-www-form-urlencoded")client := http.Client{Timeout: time.Second * 30,}res, err := client.Do(req)if err != nil {return nil, err}if res.StatusCode != http.StatusOK {return nil, errors.New("could not retrieve token")}resBody, err := ioutil.ReadAll(res.Body)if err != nil {return nil, err}var GoogleOauthTokenRes map[string]interface{}if err := json.Unmarshal(resBody, &GoogleOauthTokenRes); err != nil {return nil, err}tokenBody := &GoogleOauthToken{Access_token: GoogleOauthTokenRes["access_token"].(string),Id_token: GoogleOauthTokenRes["id_token"].(string),}return tokenBody, nil}func GetGoogleUser(access_token string, id_token string) (*GoogleUserResult, error) {rootUrl := fmt.Sprintf("https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token=%s", access_token)req, err := http.NewRequest("GET", rootUrl, nil)if err != nil {return nil, err}req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", id_token))client := http.Client{Timeout: time.Second * 30,}res, err := client.Do(req)if err != nil {return nil, err}if res.StatusCode != http.StatusOK {return nil, errors.New("could not retrieve user")}resBody, err := ioutil.ReadAll(res.Body)if err != nil {return nil, err}var GoogleUserRes map[string]interface{}if err := json.Unmarshal(resBody, &GoogleUserRes); err != nil {return nil, err}userBody := &GoogleUserResult{Id: GoogleUserRes["id"].(string),Email: GoogleUserRes["email"].(string),Verified_email: GoogleUserRes["verified_email"].(bool),Name: GoogleUserRes["name"].(string),Given_name: GoogleUserRes["given_name"].(string),Picture: GoogleUserRes["picture"].(string),Locale: GoogleUserRes["locale"].(string),}return userBody, nil}
Update the User Model
If you followed the previous tutorials in this series then update themodels/user.model.go
file to have thePhoto
andProvider
fields.
When a user registers an account through Google OAuth, we’ll set theprovider
field toGoogle
whereas a normal user who registers with email and password will have theprovider
to belocal
.
models/user.model.ts
type SignUpInput struct {Name string `json:"name" bson:"name" binding:"required"`Email string `json:"email" bson:"email" binding:"required"`Password string `json:"password" bson:"password" binding:"required,min=8"`PasswordConfirm string `json:"passwordConfirm" bson:"passwordConfirm,omitempty" binding:"required"`Role string `json:"role" bson:"role"`Provider string `json:"provider,omitempty" bson:"provider,omitempty"`Photo string `json:"photo,omitempty" bson:"photo,omitempty"`Verified bool `json:"verified" bson:"verified"`CreatedAt time.Time `json:"created_at" bson:"created_at"`UpdatedAt time.Time `json:"updated_at" bson:"updated_at"`}type SignInInput struct {Email string `json:"email" bson:"email" binding:"required"`Password string `json:"password" bson:"password" binding:"required"`}type DBResponse struct {ID primitive.ObjectID `json:"id" bson:"_id"`Name string `json:"name" bson:"name"`Email string `json:"email" bson:"email"`Password string `json:"password" bson:"password"`PasswordConfirm string `json:"passwordConfirm,omitempty" bson:"passwordConfirm,omitempty"`Provider string `json:"provider" bson:"provider"`Photo string `json:"photo,omitempty" bson:"photo,omitempty"`Role string `json:"role" bson:"role"`Verified bool `json:"verified" bson:"verified"`CreatedAt time.Time `json:"created_at" bson:"created_at"`UpdatedAt time.Time `json:"updated_at" bson:"updated_at"`}type UserResponse struct {ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"`Name string `json:"name,omitempty" bson:"name,omitempty"`Email string `json:"email,omitempty" bson:"email,omitempty"`Role string `json:"role,omitempty" bson:"role,omitempty"`Photo string `json:"photo,omitempty" bson:"photo,omitempty"`Provider string `json:"provider" bson:"provider"`CreatedAt time.Time `json:"created_at" bson:"created_at"`UpdatedAt time.Time `json:"updated_at" bson:"updated_at"`}type UpdateDBUser struct {ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"`Name string `json:"name,omitempty" bson:"name,omitempty"`Email string `json:"email,omitempty" bson:"email,omitempty"`Password string `json:"password,omitempty" bson:"password,omitempty"`PasswordConfirm string `json:"passwordConfirm,omitempty" bson:"passwordConfirm,omitempty"`Role string `json:"role,omitempty" bson:"role,omitempty"`Provider string `json:"provider" bson:"provider"`Photo string `json:"photo,omitempty" bson:"photo,omitempty"`Verified bool `json:"verified,omitempty" bson:"verified,omitempty"`CreatedAt time.Time `json:"created_at,omitempty" bson:"created_at,omitempty"`UpdatedAt time.Time `json:"updated_at,omitempty" bson:"updated_at,omitempty"`}func FilteredResponse(user *DBResponse) UserResponse {return UserResponse{ID: user.ID,Email: user.Email,Name: user.Name,Role: user.Role,Provider: user.Provider,Photo: user.Photo,CreatedAt: user.CreatedAt,UpdatedAt: user.UpdatedAt,}}
Add a Service to Upsert the User
First, let’s create a helper function to marshal and unmarshal the struct into a BSON document.
utils/helper.go
func ToDoc(v interface{}) (doc *bson.D, err error) {data, err := bson.Marshal(v)if err != nil {return}err = bson.Unmarshal(data, &doc)return}
Next, add theUpsertUser
method to theUserService
interface.
services/user.service.go
type UserService interface {FindUserById(string) (*models.DBResponse, error)FindUserByEmail(string) (*models.DBResponse, error)UpsertUser(string, *models.UpdateDBUser) (*models.DBResponse, error)}
Now add the code below to theservices/user.service.impl.go
file to enable us to upsert the user’s information in the MongoDB database.
services/user.service.impl.go
type UserServiceImpl struct {collection *mongo.Collectionctx context.Context}func NewUserServiceImpl(collection *mongo.Collection, ctx context.Context) UserService {return &UserServiceImpl{collection, ctx}}// FindUserByID// FindUserByEmail// UpsertUserfunc (uc *UserServiceImpl) UpsertUser(email string, data *models.UpdateDBUser) (*models.DBResponse, error) {doc, err := utils.ToDoc(data)if err != nil {return nil, err}opts := options.FindOneAndUpdate().SetUpsert(true).SetReturnDocument(1)query := bson.D{{Key: "email", Value: email}}update := bson.D{{Key: "$set", Value: doc}}res := uc.collection.FindOneAndUpdate(uc.ctx, query, update, opts)var updatedPost *models.DBResponseif err := res.Decode(&updatedPost); err != nil {return nil, errors.New("no post with that Id exists")}return updatedPost, nil}
In the above code, you’ll notice that we used.SetUpsert(true)
on the FindOneAndUpdateOptions instance returned byoptions.FindOneAndUpdate()
.
Adding.SetUpsert(true)
will instruct the MongoDB server to create the user if the email used in the BSON query doesn’t exist. On the other hand, MongoDB will only update the user’s data if that email already exists in the database.
Create the Google OAuth Controller
To generate the JWT tokens, we will use theGolang JWT package. In the future, the version might change so visit theirGitHub page to see the current version.
go get -u github.com/golang-jwt/jwt/v4
Functions to sign and verify the access and refresh tokens
utils/token.go
func CreateToken(ttl time.Duration, payload interface{}, privateKey string) (string, error) {decodedPrivateKey, err := base64.StdEncoding.DecodeString(privateKey)if err != nil {return "", fmt.Errorf("could not decode key: %w", err)}key, err := jwt.ParseRSAPrivateKeyFromPEM(decodedPrivateKey)if err != nil {return "", fmt.Errorf("create: parse key: %w", err)}now := time.Now().UTC()claims := make(jwt.MapClaims)claims["sub"] = payloadclaims["exp"] = now.Add(ttl).Unix()claims["iat"] = now.Unix()claims["nbf"] = now.Unix()token, err := jwt.NewWithClaims(jwt.SigningMethodRS256, claims).SignedString(key)if err != nil {return "", fmt.Errorf("create: sign token: %w", err)}return token, nil}func ValidateToken(token string, publicKey string) (interface{}, error) {decodedPublicKey, err := base64.StdEncoding.DecodeString(publicKey)if err != nil {return nil, fmt.Errorf("could not decode: %w", err)}key, err := jwt.ParseRSAPublicKeyFromPEM(decodedPublicKey)if err != nil {return "", fmt.Errorf("validate: parse key: %w", err)}parsedToken, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) {if _, ok := t.Method.(*jwt.SigningMethodRSA); !ok {return nil, fmt.Errorf("unexpected method: %s", t.Header["alg"])}return key, nil})if err != nil {return nil, fmt.Errorf("validate: %w", err)}claims, ok := parsedToken.Claims.(jwt.MapClaims)if !ok || !parsedToken.Valid {return nil, fmt.Errorf("validate: invalid token")}return claims["sub"], nil}
Now let’s add theGoogleOAuth
controller to thecontrollers/auth.controller.go
file. This controller will be evoked when the user is redirected to the server.
controllers/auth.controller.ts
type AuthController struct {authService services.AuthServiceuserService services.UserService}func NewAuthController(authService services.AuthService, userService services.UserService) AuthController {return AuthController{authService, userService}}// SignUp User// SignIn User// Refresh Access Tokenfunc (ac *AuthController) GoogleOAuth(ctx *gin.Context) {code := ctx.Query("code")var pathUrl string = "/"if ctx.Query("state") != "" {pathUrl = ctx.Query("state")}if code == "" {ctx.JSON(http.StatusUnauthorized, gin.H{"status": "fail", "message": "Authorization code not provided!"})return}// Use the code to get the id and access tokenstokenRes, err := utils.GetGoogleOauthToken(code)if err != nil {ctx.JSON(http.StatusBadGateway, gin.H{"status": "fail", "message": err.Error()})}user, err := utils.GetGoogleUser(tokenRes.Access_token, tokenRes.Id_token)if err != nil {ctx.JSON(http.StatusBadGateway, gin.H{"status": "fail", "message": err.Error()})}createdAt := time.Now()resBody := &models.UpdateDBUser{Email: user.Email,Name: user.Name,Photo: user.Picture,Provider: "google",Role: "user",Verified: true,CreatedAt: createdAt,UpdatedAt: createdAt,}updatedUser, err := ac.userService.UpsertUser(user.Email, resBody)if err != nil {ctx.JSON(http.StatusBadGateway, gin.H{"status": "fail", "message": err.Error()})}config, _ := config.LoadConfig(".")// Generate Tokensaccess_token, err := utils.CreateToken(config.AccessTokenExpiresIn, updatedUser.ID.Hex(), config.AccessTokenPrivateKey)if err != nil {ctx.JSON(http.StatusBadRequest, gin.H{"status": "fail", "message": err.Error()})return}refresh_token, err := utils.CreateToken(config.RefreshTokenExpiresIn, updatedUser.ID.Hex(), config.RefreshTokenPrivateKey)if err != nil {ctx.JSON(http.StatusBadRequest, gin.H{"status": "fail", "message": err.Error()})return}ctx.SetCookie("access_token", access_token, config.AccessTokenMaxAge*60, "/", "localhost", false, true)ctx.SetCookie("refresh_token", refresh_token, config.RefreshTokenMaxAge*60, "/", "localhost", false, true)ctx.SetCookie("logged_in", "true", config.AccessTokenMaxAge*60, "/", "localhost", false, false)ctx.Redirect(http.StatusTemporaryRedirect, fmt.Sprint(config.ClientOrigin, pathUrl))}
Create the Route
Now create aroutes/session.routes.go
file and add the code below. We have to create a new route file because later we will be adding GitHub and Facebook OAuth and it makes a lot of sense to group them in a single route file.
routes/session.routes.go
type SessionRouteController struct {authController controllers.AuthController}func NewSessionRouteController(authController controllers.AuthController) SessionRouteController {return SessionRouteController{authController}}func (rc *SessionRouteController) SessionRoute(rg *gin.RouterGroup) {router := rg.Group("/sessions/oauth")router.GET("/google", rc.authController.GoogleOAuth)}
Register the Session Router
Next, add the router defined above to the middleware stack in themain.go
file. Also, remember to install thecors package to enable you to make requests from the React.js app.
var (server *gin.Enginectx context.Contextmongoclient *mongo.Clientredisclient *redis.ClientuserService services.UserServiceUserController controllers.UserControllerUserRouteController routes.UserRouteControllerauthCollection *mongo.CollectionauthService services.AuthServiceAuthController controllers.AuthControllerAuthRouteController routes.AuthRouteControllerSessionRouteController routes.SessionRouteController)func init() {config, err := config.LoadConfig(".")if err != nil {log.Fatal("Could not load environment variables", err)}ctx = context.TODO()// Connect to MongoDBmongoconn := options.Client().ApplyURI(config.DBUri)mongoclient, err := mongo.Connect(ctx, mongoconn)if err != nil {panic(err)}if err := mongoclient.Ping(ctx, readpref.Primary()); err != nil {panic(err)}fmt.Println("MongoDB successfully connected...")// Connect to Redisredisclient = redis.NewClient(&redis.Options{Addr: config.RedisUri,})if _, err := redisclient.Ping(ctx).Result(); err != nil {panic(err)}err = redisclient.Set(ctx, "test", "Welcome to Golang with Redis and MongoDB", 0).Err()if err != nil {panic(err)}fmt.Println("Redis client connected successfully...")// CollectionsauthCollection = mongoclient.Database("golang_mongodb").Collection("users")userService = services.NewUserServiceImpl(authCollection, ctx)authService = services.NewAuthService(authCollection, ctx)AuthController = controllers.NewAuthController(authService, userService)AuthRouteController = routes.NewAuthRouteController(AuthController)SessionRouteController = routes.NewSessionRouteController(AuthController)UserController = controllers.NewUserController(userService)UserRouteController = routes.NewRouteUserController(UserController)server = gin.Default()}func main() {config, err := config.LoadConfig(".")if err != nil {log.Fatal("Could not load config", err)}defer mongoclient.Disconnect(ctx)value, err := redisclient.Get(ctx, "test").Result()if err == redis.Nil {fmt.Println("key: test does not exist")} else if err != nil {panic(err)}corsConfig := cors.DefaultConfig()corsConfig.AllowOrigins = []string{"http://localhost:8000", "http://localhost:3000"}corsConfig.AllowCredentials = trueserver.Use(cors.New(corsConfig))router := server.Group("/api")router.GET("/healthchecker", func(ctx *gin.Context) {ctx.JSON(http.StatusOK, gin.H{"status": "success", "message": value})})AuthRouteController.AuthRoute(router)UserRouteController.UserRoute(router, userService)SessionRouteController.SessionRoute(router)log.Fatal(server.Run(":" + config.Port))}
Conclusion
Congrats for reaching the end. In this article, you learned how to add Google OAuth Authentication to your React.js, Golang, and MongoDB applications.
You can find the complete code used in thistutorial on GitHub
GitHub OAuth Authentication Vue.js and Node.js (No Passport)
Google OAuth Authentication With Vue.js and Golang
3 Comments
- Alexon March 5, 2023Reply
Great tutorial! I notice that the ‘state’ is never really explained, could you perhaps include something about it?
- Edemon March 5, 2023Reply
Thank you for taking the time to read my tutorial and leave a comment! I appreciate your feedback and am happy to explain the state parameter in more detail.
In short, the state parameter is an important security feature in the Google OAuth flow. When we generate the consent screen URL, we include a random string of characters in the state parameter.
This helps us ensure that the authorization code we receive later comes from the same place that requested it originally. By doing this, we can protect against cross-site request forgery (CSRF) attacks, where a malicious third party tries to trick the user into granting access to their data.
Here’s how it works: When a user clicks “Allow” on the consent screen, Google sends an authorization code back to our server, along with the state parameter. We can then compare the value of the state parameter that we originally sent with the value of the state parameter that Google returned.
If they match, then we know that the authorization code is legitimate and came from the same place that requested it. If they don’t match, we can reject the authorization code and prevent any unauthorized access to our user’s data.
The state parameter also allows us to redirect the user to a protected page after they’ve been authenticated. For example, if a user was trying to access a page that requires authentication, we can store the protected page’s URL in the state parameter. After the user has been authenticated, we can redirect them to the protected page by extracting the URL from the state parameter.
I hope this explanation helps clarify the importance of the state parameter in the Google OAuth flow. If you have any more questions or feedback, please don’t hesitate to let me know!
- Alexon March 5, 2023Reply
Hi Edem, thanks for the explanation! I found ‘let from = ((location.state as any)?.from?.pathname as string) || ‘/’;’ in the login page code, it all makes sense!
Leave a ReplyCancel reply
This site uses Akismet to reduce spam.Learn how your comment data is processed.
Support Me!

Recent posts
Categories
- C#(2)
- C++(1)
- CSS / SCSS(3)
- Deno(8)
- Golang(31)
- JavaScript(5)
- NextJs(38)
- NodeJS(32)
- Programming(19)
- Python(19)
- React(38)
- Rust(35)
- Svelte(5)
- Vue(7)