Google OAuth components must be used within GoogleOAuthProvider - reactjs

I want to build my next js project in which i am using
https://www.npmjs.com/package/#react-oauth/google
but when I build it i get the following :
this is layout.js and in _app.js I have all the components wrapped in GoogleOAuthProvider
import { GoogleLogin } from '#react-oauth/google';
import {FcGoogle} from "react-icons/Fc"
import { useGoogleLogin } from '#react-oauth/google';
export default function Layout({ children }) {
const client_id = ""
const responseGoogle = (response) => {
console.log(response);
}
CUTTED (NOT RELEVANT)
const login = useGoogleLogin({
onSuccess: codeResponse => {
const { code } = codeResponse;
console.log(codeResponse)
axios.post("http://localhost:8080/api/create-tokens", { code }).then(response => {
const { res, tokens } = response.data;
const refresh_token = tokens["refresh_token"];
const db = getFirestore(app)
updateDoc(doc(db, 'links', handle), {
refresh_token : refresh_token
})
updateDoc(doc(db, 'users', useruid), {
refresh_token : refresh_token
}).then(
CUTTED (NOT RELEVANT)
)
}).catch(err => {
console.log(err.message);
})
},
onError: errorResponse => console.log(errorResponse),
flow: "auth-code",
scope: "https://www.googleapis.com/auth/calendar"
});
return (
<>
CUTTED (NOT RELEVANT)
</>
)
}
Everything works perfect in dev mode but it does not want to build

I've faced this issue too. So I use 'GoogleLogin' instead of 'useGoogleLogin', then you can custom POST method on 'onSuccess' property.
import { GoogleLogin, GoogleOAuthenProvider} from '#react-oauth/google';
return(
<GoogleOAuthProvider clientId="YOUR CLIENT ID">
<GoogleLogin
onSuccess={handleLogin}
/>
</GoogleOAuthProvider>
The async function will be like...
const handleLogin = async = (credentialResponse) => {
var obj = jwt_decode(credentialResponse.credential);
var data = JSON.stringify(obj);
console.log(data);
const data = {your data to send to server};
const config = {
method: 'POST',
url: 'your backend server or endpoint',
headers: {},
data: data
}
await axios(config)
}
Spending whole day, this solve me out. Just want to share.

You have to wrap your application within GoogleOAuthProvider component. Please keep in mind that you will need your client ID for this.
import { GoogleOAuthProvider } from '#react-oauth/google';
<GoogleOAuthProvider clientId="<your_client_id>">
<SomeComponent />
...
<GoogleLoginButton onClick={handleGoogleLogin}/>
</GoogleOAuthProvider>;

Related

Remix.run, Remix-Auth destroy user session if token validation fails

I am using Remix, along with Remix-Auth and using the Twitch API/OAuth, which requires that I check in with their /validate endpoint every hour docs. I had someone recommend that I use a resource route and POST to that if the validation endpoint returned a status of 401, however, I need as I stated before the request needs to be sent every hour I figured maybe I could use something like React-Query to POST to the resource route every hour.
Just pointing out that I use createCookieSessionStorage with Remix Auth to create the session
Problem
I haven't been able to achieve the actual session being destroyed and a user being re-routed to the login page, I have left what actual code I have currently any help or suggestions to actually achieve the session being destroyed and be re-routed to the login page if the validation fails would be greatly appreciated.
// React Query client side, checks if the users token is still valid
const { error, data } = useQuery("TV-Revalidate", () =>
fetch("https://id.twitch.tv/oauth2/validate", {
headers: {
Authorization: `Bearer ${user?.token}`,
},
}).then((res) => res.json())
);
The above React Query returns this
// My attempt at the resource route
// ~routes/auth/destroy.server.ts
import { ActionFunction, redirect } from "#remix-run/node";
import { destroySession, getSession } from "~/services/session.server";
export const action: ActionFunction = async ({request}) => {
const session = await getSession(request.headers.get("cookie"))
return redirect("/login", {
headers: {
"Set-Cookie": await destroySession(session)
}
})
}
// Second attempt at resource route
// ~routes/auth/destroy.server.ts
import { ActionFunction, redirect } from "#remix-run/node";
import { destroySession, getSession } from "~/services/session.server";
export const action: ActionFunction = async ({request}) => {
const session = await getSession(request.headers.get("cookie"))
return destroySession(session)
}
I attempted using an if statement to POST to the resource route or else render the page, however, this definitely won't work as React errors out because functions aren't valid as a child and page is blank.
//index.tsx
export default function Index() {
const { user, bits, vali } = useLoaderData();
console.log("loader", vali);
const { error, data } = useQuery("TV-Revalidate", () =>
fetch("https://id.twitch.tv/oauth2/validate", {
headers: {
Authorization: `Bearer ${user?.token}`,
},
}).then((res) => res.json())
);
if (data?.status === 401)
return async () => {
await fetch("~/services/destroy.server", { method: "POST" });
};
else
return ( ... );}
You could use Remix' useFetcher hook.
https://remix.run/docs/en/v1/api/remix#usefetcher
// Resource route
// routes/api/validate
export const loader: LoaderFunction = async ({ request }) => {
const session = await getSession(request);
try {
const { data } = await fetch("https://id.twitch.tv/oauth2/validate", {
headers: {
Authorization: `Bearer ${session.get("token")}`
}
});
return json({
data
}, {
headers: {
"Set-Cookie": await commitSession(session),
}
});
} catch(error) {
return redirect("/login", {
headers: {
"Set-Cookie": await destroySession(session)
}
});
}
}
And then in your route component something like this:
const fetcher = useFetcher();
useEffect(() => {
if (fetcher.type === 'init') {
fetcher.load('/api/validate');
}
}, [fetcher]);
useEffect(() => {
if(fetcher.data?.someValue {
const timeout = setTimeout(() => fetcher.load('/api/validate'), 1 * 60 * 60 * 1000);
return () => clearTimeout(timeout);
}
},[fetcher.data]);

Embed code onto another page and use their variables

I have created a web app in React with several pages. To make it a bit secure for our department, at least so everyone can't enter the site and see what's going on, there are some code that does some checks, and also creates some variables I can use for each of my pages.
The issue is that this code is the same on every single page.
So basically it looks something like:
const MyApp= () => {
const { instance, accounts, inProgress } = useMsal();
const [accessToken, setAccessToken] = useState<any>(null);
const name = accounts[0] && accounts[0].name;
const email = accounts[0] && accounts[0].username;
const isAuthenticated = useIsAuthenticated();
useEffect(() => {
const request = {
...loginRequest,
account: accounts[0],
};
instance
.acquireTokenSilent(request)
.then((response: any) => {
setAccessToken(response.accessToken);
})
.catch(() => {
instance.acquireTokenPopup(request).then((response: any) => {
setAccessToken(response.accessToken);
});
});
}, [isAuthenticated]);
function POST(path: string, data: any) {
return fetch(`${fetchUrl}${path}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: accessToken,
},
body: JSON.stringify(data),
});
}
<something else>
return (
<>
<div>{name}</div>
<div>{email}</div>
</>
);
};
export default MyApp;
And it's just annoying to write this on every page. So is there some way to create some kind of component, and then embed it into all pages, where I still have the option to use the const variables created ?

How to fetch API data from Axios inside the getServerSideProps function in NextJS?

I'm building an App with Next.js, and I need to connect to specific API routes (set up with API Platform) and populate pages with the route's responses.
The API is working fine, but no matter how I try to implement my Axios call inside the getServerSideProps, I always get the same error, ECONNREFUSED, from my Node stack.
I tried to get the data from useEffect() and it's working fine, but I would like to know if there's a way to call it directly in getServerSideProps.
I'm using a Node container for Docker, and the routes are authenticated through a JWT Token (stored in the session and the client cookies for the server-side connection)
Here are is my code:
pages/accounts.js:
export async function getServerSideProps(context) {
const cookies = new Cookies(context.req.headers.cookie)
const adminToken = cookies.get('jwtToken')
const res = await getAllAccounts(adminToken)
return {
props: {
testdata: ''
},
}
}
lib/accounts.js:
import service from '../service'
export const getAllAccounts = async (adminToken) => {
const res = service({ jwtToken : adminToken }).get(`/accounts`).then((response) => {
}).catch((error) => {
console.dir(error)
})
}
HTTP wrapper:
import axios from 'axios';
import jwt_decode from "jwt-decode";
import mockAdapter from 'axios-mock-adapter';
const service = ({ jwtToken = null, store = null, mockURL = null, mockResponse = null, multipart = false } = {}) => {
const options = {};
options.baseURL = process.env.NEXT_PUBLIC_API_URL + '/api';
if(multipart === true) {
options.headers = {
'Content-Type': 'multipart/form-data'
}
} else {
options.headers = {
'Content-Type': 'application/ld+json',
accept: 'application/ld+json'
}
}
const instance = axios.create(options);
instance.interceptors.response.use(response => {
return response;
}, error => {
return Promise.reject(error);
})
if (mockURL !== null && mockResponse !== null) {
let mock = new mockAdapter(instance);
mock.onAny(mockURL).reply(200, mockResponse)
}
return instance;
};
export default service;
Through the error dump in the node stack, I managed to see that the request headers are correct, and the JWT correctly passed through.
Do not use Axios. Just use fetch().
Next.js polyfills fetch() by default on both the client and server, so you can just use it:
In addition to fetch() on the client-side, Next.js polyfills fetch() in the Node.js environment. You can use fetch() in your server code (such as getStaticProps/getServerSideProps) without using polyfills such as isomorphic-unfetch or node-fetch.
Source.
getServerSideProps works well with axios if we return response.data
export const getServerSideProps: GetStaticProps = async ({ params }) => {
const { brandName } = params as IParams;
const brandData = await $host.get(`api/brand/${brandName}`).then(response => response.data);
return {
props: {
brand: brandData,
},
};
};
Your problem is that your async method does not return a promise.
import service from '../service'
export const getAllAccounts = async (adminToken) => {
const res = service({ jwtToken : adminToken }).get(`/accounts`);
return res;
}
In my NextJS begining I followed this tutorial , and I changed fetch to axios in this way:
export const getStaticPaths = async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/users');
const data = await res.json();
const paths = data.map((ninja) => {
return {
params: { id: ninja.id.toString() },
};
});
return {
paths,
fallback: false,
};
};
export const getStaticProps = async (context) => {
const id = context.params.id;
const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
const data = await res.json();
return {
props: { ninja: data },
};
};
I applied the change using useEffect()
useEffect(() => {
// const data = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
// const res = await data.json();
// setninja(res);
const fetchData = async () => {
const result = await axios(`https://jsonplaceholder.typicode.com/users/${id}`);
setninja(result.data);
};
fetchData();
console.log(data);
}, []);
I hope this info will be useful for you.
I Used Axios in getServerSideProps without any problems.
export const getServerSideProps: GetServerSideProps = async({
params,
res
}) => {
try {
const response = await axios.get(`/api/test`);
return {
props: {
data: response.data
},
}
} catch {
res.statusCode = 404;
return {
props: {}
};
}
};

React and reCAPTCHA v3

Is there any easy way to use reCAPTCHA v3 in react? Did a google search an can only find components for v2. And only react-recaptcha-v3 for v3.
But I get an error Invalid site key or not loaded in api.js when I try to use the component.
Hey you don't need a package, its just an unnecessary package you don't need.
https://medium.com/#alexjamesdunlop/unnecessary-packages-b3623219d86
I wrote an article about why you shouldn't use it and another package.
Don't rely on some package! Rely on google instead :)
const handleLoaded = _ => {
window.grecaptcha.ready(_ => {
window.grecaptcha
.execute("_reCAPTCHA_site_key_", { action: "homepage" })
.then(token => {
// ...
})
})
}
useEffect(() => {
// Add reCaptcha
const script = document.createElement("script")
script.src = "https://www.google.com/recaptcha/api.js?render=_reCAPTCHA_site_key"
script.addEventListener("load", handleLoaded)
document.body.appendChild(script)
}, [])
return (
<div
className="g-recaptcha"
data-sitekey="_reCAPTCHA_site_key_"
data-size="invisible"
></div>
)
I am teaching myself React + TypeScript and this is what I came up with to implement recaptcha v3.
I wanted a simple solution that would allow me to:
get the token dynamically only when the form is submitted to avoid timeouts and duplicate token errors
use recaptcha only on some components for privacy reasons (eg. login, register, forgot-password) instead of globally defining recaptcha api.js in index.html
require the least code possible to implement in a component
reCAPTCHA.ts
declare global {
interface Window {
grecaptcha: any;
}
}
export default class reCAPTCHA {
siteKey: string;
action: string;
constructor(siteKey: string, action: string) {
loadReCaptcha(siteKey);
this.siteKey = siteKey;
this.action = action;
}
async getToken(): Promise<string> {
let token = "";
await window.grecaptcha.execute(this.siteKey, {action: this.action})
.then((res: string) => {
token = res;
})
return token;
}
}
const loadReCaptcha = (siteKey: string) => {
const script = document.createElement('script')
script.src = `https://www.recaptcha.net/recaptcha/api.js?render=${siteKey}`
document.body.appendChild(script)
}
To use this class declare it as a property in the component:
recaptcha = new reCAPTCHA((process.env.REACT_APP_RECAPTCHA_SITE_KEY!), "login");
And on form submit get the token that you need to pass to backend:
let token: string = await this.recaptcha.getToken();
To verify the token on the backend:
recaptcha.ts
const fetch = require("node-fetch");
const threshold = 0.6;
export async function validateRecaptcha(recaptchaToken: string, expectedAction: string) : Promise<boolean> {
const recaptchaSecret = process.env.RECAPTCHA_SECRET_KEY;
const url = `https://www.recaptcha.net/recaptcha/api/siteverify?secret=${recaptchaSecret}&response=${recaptchaToken}`;
let valid = false;
await fetch(url, {method: 'post'})
.then((response: { json: () => any; }) => response.json())
.then((data: any)=> {
valid = (data.success && data.score && data.action && data.score >= threshold && data.action === expectedAction);
});
return valid;
}
I have very limited experience with JS/TS and React but this solution does work for me. I welcome any input on improving this code.
You can use react-google-recaptcha3 npm package (size: ~5 KB)
npm i react-google-recaptcha3
Usage
import ReactRecaptcha3 from 'react-google-recaptcha3';
const YOUR_SITE_KEY = '';
function App() {
// load google recaptcha3 script
useEffect(() => {
ReactRecaptcha3.init(YOUR_SITE_KEY).then(
(status) => {
console.log(status);
}
);
}, [])
}
Now on form submit you need to generate token and then append it to your form data
const submit = () => {
const formData = { name: "John", lastname: "Doe" }
ReactRecaptcha3.getToken().then(
(token) => {
console.log(token);
formData.token = token;
// send request to backend
fetch(url, { method: 'POST', body: JSON.stringify(formData) }).then(...)
},
(error) => {
console.log(error);
}
);
};
Now in backend you need to validate token
const request = require('request-promise');
const secretKey = YOUR_RECAPTCHA_SECRET_KEY;
const userIp = 'USER_IP';
request.get({
url: `https://www.google.com/recaptcha/api/siteverify?secret=${secretKey}&response=${recaptchaToken}&remoteip=${userIp}`,
}).then((response) => {
// If response false return error message
if (response.success === false) {
return res.json({
success: false,
error: 'Recaptcha token validation failed'
});
}
// otherwise continue handling/saving form data
next();
})
Stackblitz example
Try this one!
https://github.com/t49tran/react-google-recaptcha-v3
npm install react-google-recaptcha-v3
You can also create your own custom hook useReCaptcha with React (Typescript):
// hooks/useReCaptcha.ts
import { RECAPTCHA_KEY, RECAPTCHA_TOKEN } from 'config/config'
import { useEffect, useState } from 'react'
const showBadge = () => {
if (!window.grecaptcha) return
window.grecaptcha.ready(() => {
const badge = document.getElementsByClassName('grecaptcha-badge')[0] as HTMLElement
if (!badge) return
badge.style.display = 'block'
badge.style.zIndex = '1'
})
}
const hideBadge = () => {
if (!window.grecaptcha) return
window.grecaptcha.ready(() => {
const badge = document.getElementsByClassName('grecaptcha-badge')[0] as HTMLElement
if (!badge) return
badge.style.display = 'none'
})
}
const useReCaptcha = (): { reCaptchaLoaded: boolean; generateReCaptchaToken: (action: string) => Promise<string> } => {
const [reCaptchaLoaded, setReCaptchaLoaded] = useState(false)
// Load ReCaptcha script
useEffect(() => {
if (typeof window === 'undefined' || reCaptchaLoaded) return
if (window.grecaptcha) {
showBadge()
setReCaptchaLoaded(true)
return
}
const script = document.createElement('script')
script.async = true
script.src = `https://www.google.com/recaptcha/api.js?render=${RECAPTCHA_KEY}`
script.addEventListener('load', () => {
setReCaptchaLoaded(true)
showBadge()
})
document.body.appendChild(script)
}, [reCaptchaLoaded])
// Hide badge when unmount
useEffect(() => hideBadge, [])
// Get token
const generateReCaptchaToken = (action: string): Promise<string> => {
return new Promise((resolve, reject) => {
if (!reCaptchaLoaded) return reject(new Error('ReCaptcha not loaded'))
if (typeof window === 'undefined' || !window.grecaptcha) {
setReCaptchaLoaded(false)
return reject(new Error('ReCaptcha not loaded'))
}
window.grecaptcha.ready(() => {
window.grecaptcha.execute(RECAPTCHA_KEY, { action }).then((token: string) => {
localStorage.setItem(RECAPTCHA_TOKEN, token)
resolve(token)
})
})
})
}
return { reCaptchaLoaded, generateReCaptchaToken }
}
export default useReCaptcha
Then in the login component for example, you can call this custom hook:
// Login.ts
import React from 'react'
import useReCaptcha from 'hooks/useReCaptcha'
const LoginPageEmail = () => {
const { reCaptchaLoaded, generateReCaptchaToken } = useReCaptcha()
const login = async () => {
await generateReCaptchaToken('login') // this will create a new token in the localStorage
await callBackendToLogin() // get the token from the localStorage and pass this token to the backend (in the cookies or headers or parameter..)
}
return (
<button disabled={!reCaptchaLoaded} onClick={login}>
Login
</button>
)
}
export default LoginPageEmail

Mern Data flow problems in ReactJS and Axios

I am an aspiring react developer and I am having some issues with my app.
I am trying to scrape the New York times for an assignment I have, and I can get my data with a search to log server-side, but I cant seem to pass it back. I can get my data by pushing it to a MongoDB then querying it in a separate process from the front end, but I don't want to do that.
I want to pass the object back up the stack to the client side. Does anyone know how I might accomplish that?
here is some of my code.
my dir structure:
here is the client folder structure:
here is my Home.jsx file clientside in /pages:
import React, { Component } from 'react';
import { Container, Row, Column } from '../../components/BootstrapGrid';
import API from '../../utils/API'
import {Input, FormBtn} from '../../components/Form'
class Home extends Component {
state = {
formInput: "",
posts: [],
}
loadArticles = (res) => {
console.log('res')
}
handleInputChange = event => {
const { name, value } = event.target;
this.setState({
[name]: value
});
};
handleFormSubmit = event => {
event.preventDefault();
let query = this.state.formInput
// console.log(query)
API.scrapeArticles(query)
// console.log(this.state)
};
render() {
return (
<Container>
<Row>
<Column>
</Column>
<Column>
<Input
value={this.state.formInput}
onChange={this.handleInputChange}
name="formInput"
placeholder="Search Query (required)"
/>
<FormBtn onClick={this.handleFormSubmit}>Scrape NYT API</FormBtn>
</Column>
</Row>
</Container>
);
}
}
export default Home;
here is my code calling the clientside api in client/utils/api/:
import axios from "axios";
export default {
// getPosts: function () {
// return axios.get('/api/posts')
// },
// savePost: function (postData) {
// return axios.post("/api/posts", postData);
// },
scrapeArticles: function (query) {
// console.log(query)
let queryData = {
query: query
}
return axios.post('/api/scraper', queryData)
}
};
here is my code from the backend routes/index.js being hit by axios (i think? Im honestly not sure how but i think this is the flow):
const path = require("path");
const router = require("express").Router();
const postsController = require('../controllers/postsController')
router.route("/")
.get(postsController.getAll)
.post(postsController.create);
router.route('/api/scraper')
.post(postsController.scraper)
.get(postsController.scraper)
// If no API routes are hit, send the React app
router.use(function (req, res) {
res.sendFile(path.join(__dirname, "../client/build/index.html"));
});
module.exports = router;
here is my controller that is referenced in the file above:
const scraper = require('../scraper')
const db = require('../models');
module.exports = {
create: function (req, res) {
db.Post
.create(req.body)
.then(dbmodel => res.json(dbmodel))
.catch(err => res.status(422).json(err))
},
getAll: function (req, res) {
db.Post
.find(req.query)
.sort({date: -1})
.then(dbModel => res.json(dbModel))
.catch(err => res.status(422).json(err))
},
scraper: function (req, res) {
let queryData = req.body.query
scraper(queryData)
},
scraperGet: function (req, res) {
scraper()
console.log(res.body)
}
}
and lastly, here is the scraper file on the backend:
const request = require('request');
const mongoose = require('mongoose');
const db = require('./models');
const scraper = (queryData) => {
console.log(`#scraper ${queryData}`)
let articleData = []
request.get({
url: "https://api.nytimes.com/svc/search/v2/articlesearch.json",
qs: {
'api-key': "-----------------------------",
"q" : queryData
},
}, function (err, response, body) {
body = JSON.parse(body);
let articles = body.response.docs
articles.forEach(element => {
// console.log(element)
let title= element.headline.main
let url = element.web_url
let synopsis = element.abstract
let snippet = element.snippet
let source = element.source
let pubDate = element.pub_date
let article = {
title: title,
url: url,
synopsis: synopsis,
snippet: snippet,
source: source,
pubDate: pubDate,
}
// console.log(article)
articleData.push(article)
db.Post.create({title:article.title}).then(article => {
console.log(article)
}).catch(err => {
console.log(err)
})
});
return articleData
});
}
module.exports = scraper;
So i know right now it is pushing to mongo. This is only because I couldn't figure out how to pass that data back just stored in a variable.
I really don't want to have to push all my results to the db and then make a query for them. I want to have a save article function that you only save the ones you actually want.
You should send articleData to the client and then get it in the client side using .then() method of a promise.
Something like this:
scraper: function (req, res) {
let queryData = req.body.query
const articleData = scraper(queryData)
// return your json to the client
res.json(articleData)
},
Then you should receive this data in the client side, like this:
handleFormSubmit = event => {
event.preventDefault()
let query = this.state.formInput
API.scrapeArticles(query)
.then(resp => {
this.setState({ posts: resp })
})
}

Resources