DEV Community

Cover image for React authentication with pocketbase + google-oauth + react-router + react-query
Dennis kinuthia
Dennis kinuthia

Posted on

1 1

React authentication with pocketbase + google-oauth + react-router + react-query

Intro

In this article i'll try to port over an app i made in firebase to pocketbase

1 : Authentication

In 2022 provider sign-ins should be the main auth option given the ease of use for the user and developer .
The users get to login with one click while the dev doesn't have to worry about verifying , storing and managing the user passwords.
Firebase gives us a simple way to implement this (especially in google since firebase creates you a service account with the client secret an client token configured by default ) but it can also be done in pocketbase woth a little amnual work.

1 - setup your google service account

for this tutorial i used

client secret and id screenshot

official docs
video demonstration up to 1:55

then we'll enable the google as an auth provider and provde the client id and client secret in the pocket base admin panel

Image description

2 - add the redirect routein app.tsx

Since we're using react-router-dom , we'll define a client route in App.tsx



import { useState } from 'react'
import { Query, useQuery } from 'react-query';
import { Routes, Route, BrowserRouter } from "react-router-dom";
import './App.css'
import { About } from './components/about/About';
import { Login } from './components/auth/Login';
import { Protected } from './components/auth/Protected';
import { Redirect } from './components/auth/Redirect';
import { UserType } from './components/auth/types';
import { Home } from './components/home/Home';
import { Toolbar } from './components/toolbar/Toolbar';
import { client } from './pb/config';
import { LoadingShimmer } from './components/Shared/LoadingShimmer';


function App() {


  const getUser = async()=>{
    return await client.authStore.model
  }

const userQuery = useQuery(["user"],getUser); 
  console.log("user query App.tsx==== ", userQuery)

  // console.log("client authstore",client.authStore)
const user = userQuery.data

if(userQuery.isFetching || userQuery.isFetching){
  return <LoadingShimmer/>
}

return (
    <div
    className="h-screen w-screen   scroll-bar flex-col-center 
    dark-styles transition duration-500 overflow-x-hidden "
    >
      <BrowserRouter >

        <div className="fixed top-[0px] w-[100%] z-40 p-1">
          <Toolbar />
        </div>


        <div className="w-full h-full mt-12 ">
          <Routes>
            <Route
              path="/"
               element={
                <Protected user={user}>
                  <Home />
                </Protected>
              }
            />

          <Route path="/about" element={<About />} />
          <Route path="/login" element={<Login user={user}/>} />
           <Route path="/redirect" element={<Redirect user= 
          {user}/>} /> 
          </Routes>
        </div>

      </BrowserRouter>
    </div>
  );
}

export default App



Enter fullscreen mode Exit fullscreen mode

and make the redirect and login components
Redirect.tsx



import { User, Admin } from 'pocketbase';
import React, { useEffect } from 'react'
import { useQueryClient } from 'react-query';
import { Navigate, useNavigate, useSearchParams } from 'react-router-dom';
import { client } from '../../pb/config';
import { LoadingShimmer } from '../Shared/loading/LoadingShimmer';


interface RedirectProps {
user?: User | Admin | null
}

export const Redirect: React.FC<RedirectProps> = ({user}) => {
const [loading, setLoading] = React.useState(true)
const queryClient = useQueryClient()
const navigate = useNavigate()
const [searchParams] = useSearchParams();
const code = searchParams.get('code') as string
const local_prov = JSON.parse(localStorage.getItem('provider') as string)
// this hasto match what you orovided in the oauth provider , in tis case google
let redirectUrl = 'http://localhost:3000/redirect'
useEffect(()=>{
    if (local_prov.state !== searchParams.get("state")) {
      const url = 'http://localhost:3000/login'
        if (typeof window !== 'undefined') {
            window.location.href = url;
        }
    }
    else {

      client.users.authViaOAuth2(
            local_prov.name,
            code,
            local_prov.codeVerifier,
            redirectUrl
            )
            .then((response) => {
                // console.log("authentication data === ", response)
                // udating te user rofile field in pocket base with custome data from your 
                // oauth provider in this case the avatarUrl and name
                client.records.update('profiles', response.user.profile?.id as string, {
                    name: response.meta.name,
                    avatarUrl: response.meta.avatarUrl,

                }).then((res) => {
                // console.log(" successfully updated profi;e", res)

                }).catch((e) => {
                    console.log("error updating profile  == ", e)
                })
                setLoading(false)
                // console.log("client modal after logg   == ", client.authStore.model)
                queryClient.setQueryData(['user'], client.authStore.model)
                navigate('/')

            }).catch((e) => {
                console.log("error logging in with provider  == ", e)
            })
    }

},[])
if (user) {
    return <Navigate to="/" replace />;
}
return (
 <div className='w-full h-full '>
        {loading ? <LoadingShimmer/>:null}
 </div>
);
}



Enter fullscreen mode Exit fullscreen mode

Login.tsx



import React from "react";
import { providers } from "../../pb/config";
import { useNavigate } from 'react-router-dom';
import { Admin, User } from "pocketbase";




interface LoginProps {
user?: User | Admin | null
}
interface ProvType{

    name: string
    state: string
    codeVerifier: string
    codeChallenge: string
    codeChallengeMethod: string
    authUrl: string

}

export const Login: React.FC<
  LoginProps
> = ({user}) => {
const provs = providers.authProviders;
const navigate = useNavigate()
// console.log("user in Login.tsx  ==  ",user)
if(user?.email){
  navigate('/')
}
const startLogin = (prov:ProvType) => { localStorage.setItem("provider",JSON.stringify(prov));
  const redirectUrl = "http://localhost:3000/redirect";
  const url = prov.authUrl + redirectUrl;
      // console.log("prov in button === ", prov)
      // console.log("combined url ==== >>>>>>  ",url)

    if (typeof window !== "undefined") {
      window.location.href = url;
    }
  };

  return (
    <div className="w-full h-full flex-center-col">
      <div className="text-3xl font-bold ">
        LOGIN
      </div>
      {provs &&
        provs?.map((item:any) => {
          return (
            <button 
            className="p-2 bg-purple-600"
            key={item.name}
            onClick={() => startLogin(item)}>{item.name}</button>
          );
        })}
    </div>
  );
};



Enter fullscreen mode Exit fullscreen mode

Protected.tsx



import { Admin, User } from 'pocketbase';
import React, { ReactNode } from 'react'
import { Navigate } from 'react-router-dom';

interface ProtectedProps {
    user?: User | Admin | null
children:ReactNode
}

export const Protected: React.FC<ProtectedProps> = ({user,children}) => {
if(!user?.email){
 return <Navigate to={'/login'} />
}
return (
 <div className='h-full w-full'>
  {children}
 </div>
);
}



Enter fullscreen mode Exit fullscreen mode

full code

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more