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)

Dream Big


Use any Linode offering to create something unique or silly in the DEV x Linode Hackathon 2022 and win the Wacky Wildcard category.

β†’ Join the Hackathon <-