DEV Community

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

Posted on

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

Top comments (0)