
[ad_1]
Neste artigo, você aprenderá a configurar o novo botão “Fazer login com o Google” do Google Auth em um aplicativo React.js e Express.js.
Esse novo método simplifica a maneira como os desenvolvedores implementam o Google Auth. Ele traz algumas vantagens significativas, como permitir que os usuários visualizem uma foto de perfil para selecionar a conta do Google correta — o que evita erros de inscrição e garante que seu aplicativo não seja afetado quando o Google descontinuar o antigo “Fazer login com Biblioteca JavaScript do Google” em 31 de março de 2023.
Vale a pena observar que os IDs de cliente recém-criados agora estão impedidos de usar a biblioteca de plataforma mais antiga e que o Google Auth deve ser implementado dessa maneira.
Aqui está o código-fonte para este artigo: Servidor e Cliente.
Gerar um ID de cliente e um segredo do Google
A primeira etapa a ser executada para implementar a autenticação do Google é gerar um ID de cliente e um segredo para o aplicativo que você está criando.
Passo 1
Começamos indo para o console do Google.
Passo 2
Clique no menu suspenso destacado acima. Depois disso, clique no novo projeto destacado abaixo.
etapa 3
Adicione um nome de projeto. Eu escolhi connect-google-auth-article
.
Passo 4
Clique no menu suspenso na etapa 1 para selecionar o projeto.
Etapa 5
A próxima tela que você vê deve se parecer com o exemplo abaixo. Em seguida, clique no painel.
Etapa 6
A próxima etapa é configurar o consentimento oauth. Para conseguir isso, passe o mouse em “APIs e serviços” e clique em “tela de consentimento OAuth”.
Etapa 7
Selecione o tipo de consentimento desejado. Eu escolhi externo e acertei CRIO.
Etapa 8
Depois que o consentimento for definido, clique nas credenciais para definir os detalhes do seu aplicativo. Como meu aplicativo está hospedado no localhost, defino os detalhes conforme ilustrado abaixo.
Observação: quando estiver pronto para implantar seu aplicativo, substitua o URI1 e o URI2 pelo nome de domínio que deseja usar, como https://example.com
.
Etapa 9
Depois que suas credenciais forem armazenadas com sucesso, você poderá copiar ou baixar o ID do cliente e o segredo gerados.
Configurar aplicativo React
A maneira mais fácil de inicializar um aplicativo React.js é usando Create React App.
Portanto, crie uma pasta, nomeie-a como quiser. Em seguida, abra um terminal e execute o seguinte código: npx create-react-app app
.
Configurando o servidor expresso
Crie outra pasta no diretório raiz. estou nomeando o meu server
. Em seguida, abra um terminal e cd no servidor: cd server
.
Depois disso, crie um server.js
arquivo antes de gerar um package.json
Correndo npm init -y
. Em seguida, instale os seguintes pacotes:
- Express.js: “uma estrutura de aplicativo web Node.js mínima e flexível que fornece um conjunto robusto de recursos para aplicativos web e móveis”.
- CORS: um pacote Node.js para fornecer um middleware Connect/Express que pode ser usado para habilitar o compartilhamento de recursos entre origens com várias opções.
- Dotenv: um pacote Node.js que carrega variáveis de ambiente de
.env
Arquivo. - Google-auth-library: biblioteca cliente de autenticação da API do Google para Node.js.
- Jsonwebtoken: uma biblioteca de implementação de JSON Web Token para Node.js.
- Nodemon: um script de monitor simples para uso durante o desenvolvimento de um aplicativo Node.js.
Você pode instalar os pacotes acima executando o seguinte comando:
npm install express cors dotenv google-auth-library jsonwebtoken nodemon
Depois disso, configure seu script fazendo isso:
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
Sua package.json
deve ficar assim:
{
"name": "connect-google-auth-article",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^16.0.2",
"express": "^4.18.1",
"google-auth-library": "^8.5.2",
"jsonwebtoken": "^8.5.1",
"nodemon": "^2.0.20"
},
"keywords": [],
"author": "",
"license": "ISC"
}
Depois disso, escreva o seguinte código em server.js
e corra npm run dev
para iniciar seu servidor:
const express = require("express");
const app = express();
require("dotenv/config");
const cors = require("cors");
const { OAuth2Client } = require("google-auth-library");
const jwt = require("jsonwebtoken");
app.use(
cors({
origin: ["http://localhost:3000"],
methods: "GET,POST,PUT,DELETE,OPTIONS",
})
);
app.use(express.json());
let DB = [];
app.listen("5152", () => console.log("Server running on port 5152"));
Preparando o aplicativo React
Para preparar nosso aplicativo cliente, adicionaremos o script do Google ao cabeçalho de nosso public/index.html
Arquivo:
<script src="https://accounts.google.com/gsi/client" async defer></script>
Nosso index.html
arquivo deve ficar assim:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<script src="https://accounts.google.com/gsi/client" async defer></script>
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
Em seguida, vamos criar duas pastas em nosso src
: screens
e hooks
.
o screens
pasta conterá cinco arquivos: Home.jsx
, Landing.jsx
, Login.jsx
, Signup.jsx
e index.js
. o hooks
pasta conterá apenas um arquivo: useFetch.jsx
.
Configurar roteamento do lado do cliente
O pacote que usaremos para o roteamento do lado do cliente é react-router-dom
. Abra um novo terminal, cd no aplicativo e execute o seguinte código: npm install react-router-dom
.
Podemos então atualizar nosso App.js
para ficar assim:
import React, { useEffect } from "react";
import { useState } from "react";
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
const App = () => {
const [user, setUser] = useState({});
return (
<BrowserRouter>
<Routes>
</Routes>
</BrowserRouter>
);
};
export default App;
Criando a página de destino
A página de destino em nosso caso é a única página disponível para um usuário não autenticado. Ele conterá links para as páginas de inscrição e login. Isso parecerá assim:
import React from "react";
import { Link } from "react-router-dom";
const Landing = () => {
return (
<>
<header style={{ textAlign: "center" }}>
<h1>Welcome to my world</h1>
</header>
<main style={{ display: "flex", justifyContent: "center", gap: "2rem" }}>
<Link
to="/signup"
style={{
textDecoration: "none",
border: "1px solid gray",
padding: "0.5rem 1rem",
backgroundColor: "wheat",
color: "#333",
}}
>
Sign Up
</Link>
<Link
to="/login"
style={{
textDecoration: "none",
border: "1px solid gray",
padding: "0.5rem 1rem",
backgroundColor: "whitesmoke",
color: "#333",
}}
>
Login
</Link>
</main>
</>
);
};
export default Landing;
Vamos decompô-lo:
- O componente retorna um elemento de fragmento React representado por uma tag vazia.
- O fragmento contém dois elementos:
<header>
e<main>
. O cabeçalho retorna um<h1>
e centraliza o texto nele, enquanto o elemento principal retorna dois links dereact-router-dom
e também os centraliza. - Uma cor de fundo diferente é fornecida para os dois links para melhorar o UX.
A seguir, podemos abrir o screens/index.js
arquivo e exporte o Landing.jsx
igual a:
export { default as Landing } from "./Landing";
Depois disso, podemos importá-lo para o App.js
arquivo, onde configuramos uma rota para ele:
import { Landing } from "./screens";
Também:
<Route
path="https://www.sitepoint.com/"
element={user?.email ? <Navigate to="/home" /> : <Landing />}
/>
Criando um gancho useFetch
UMA gancho no React é um tipo especial de função que permite usar a funcionalidade do React. Para criar um gancho, abra hooks/useFetch.jsx
e adicione o seguinte código:
import { useState } from "react";
const useFetch = (url) => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const handleGoogle = async (response) => {
console.log(response)
};
return { loading, error, handleGoogle };
};
export default useFetch;
Criando página de inscrição
Abra o screens/Signup.jsx
arquivo e adicione o seguinte código:
import React, { useEffect } from "react";
import { Link } from "react-router-dom";
import useFetch from "../hooks/useFetch";
const SignUp = () => {
const { handleGoogle, loading, error } = useFetch(
"http://localhost:5152/signup"
);
useEffect(() => {
if (window.google) {
google.accounts.id.initialize({
client_id: process.env.REACT_APP_GOOGLE_CLIENT_ID,
callback: handleGoogle,
});
google.accounts.id.renderButton(document.getElementById("signUpDiv"), {
theme: "filled_black",
text: "continue_with",
shape: "pill",
});
}
}, [handleGoogle]);
return (
<>
<nav style={{ padding: "2rem" }}>
<Link to="https://www.sitepoint.com/">Go Back</Link>
</nav>
<header style={{ textAlign: "center" }}>
<h1>Register to continue</h1>
</header>
<main
style={{
display: "flex",
justifyContent: "center",
flexDirection: "column",
alignItems: "center",
}}
>
{error && <p style={{ color: "red" }}>{error}</p>}
{loading ? (
<div>Loading....</div>
) : (
<div id="signUpDiv" data-text="signup_with"></div>
)}
</main>
<footer></footer>
</>
);
};
export default SignUp;
Vamos decompô-lo:
- Extraímos os estados e funções disponíveis do
useFetch
gancho. Também passamos a URL que chamaremos para lidar com nosso logon no servidor. - Dentro
useEffect
verificamos a disponibilidade do script do Google — tratado pelo script que colocamos nopublic.index.html
Arquivo. - Usamos então o
initialize
método disponível no script para lidar com a funcionalidade do botão de autenticação. - Também passamos uma função de retorno de chamada, que já definimos no
useFetch
gancho.
A seguir, usaremos o renderButton
método para exibir nosso botão de autenticação na tela. O primeiro parâmetro que passamos é o elemento no qual o botão será embutido, usando o getElementById
método. Os próximos parâmetros que podemos passar são usados para personalizar a aparência do botão. Ele tem a seguinte configuração necessária:
type
: aceita dois valores — padrão e ícone.
Além disso, possui configurações opcionais, incluindo o seguinte:
theme
: o tema do botão. Ele pode aceitar um dos seguintes:filled_blue
,outline
efilled_black
.size
: define o tamanho do botão. Ele aceitalarge
,medium
esmall
.text
: define o texto do botão. Ele aceita um dos seguintes:signin_with
,signup_with
,continue_with
esignin
.shape
: define a forma do botão. Ele aceitarectangular
,pill
,circle
ousquare
.logo_alignment
: define como o logotipo será colocado no botão. Ele pode aceitarleft
oucenter
.width
: define a largura do botão. Vale a pena notar que a largura máxima é 400.
Outra opção é locale
que é usado para definir um idioma específico.
Também verificamos a disponibilidade de um erro e o exibimos ao usuário. Também verificamos o estado de carregamento.
Criando a página de login
A página de login é semelhante à página de inscrição. A única diferença é a URL do servidor e o texto do botão. O código deve ficar assim:
import React, { useEffect } from "react";
import { Link } from "react-router-dom";
import useFetch from "../hooks/useFetch";
const Login = () => {
const { handleGoogle, loading, error } = useFetch(
"http://localhost:5152/login"
);
useEffect(() => {
if (window.google) {
google.accounts.id.initialize({
client_id: process.env.REACT_APP_GOOGLE_CLIENT_ID,
callback: handleGoogle,
});
google.accounts.id.renderButton(document.getElementById("loginDiv"), {
theme: "filled_black",
text: "signin_with",
shape: "pill",
});
}
}, [handleGoogle]);
return (
<>
<nav style={{ padding: "2rem" }}>
<Link to="https://www.sitepoint.com/">Go Back</Link>
</nav>
<header style={{ textAlign: "center" }}>
<h1>Login to continue</h1>
</header>
<main
style={{
display: "flex",
justifyContent: "center",
flexDirection: "column",
alignItems: "center",
}}
>
{error && <p style={{ color: "red" }}>{error}</p>}
{loading ? <div>Loading....</div> : <div id="loginDiv"></div>}
</main>
<footer></footer>
</>
);
};
export default Login;
Note o google.accounts.id.prompt()
é usado para solicitar automaticamente ao usuário que faça login imediatamente ao abrir sua página da web. Ele pode ser colocado no arquivo raiz ou na página de login.
Crie também um .env.local
arquivo na pasta raiz e adicione o seguinte:
REACT_APP_GOOGLE_CLIENT_ID=your client id
Em seguida, exportamos a página de inscrição e login do screens.index.js
Arquivo:
export { default as Login } from "./Login";
export { default as Signup } from "./SignUp";
Depois disso, configuramos suas rotas no App.js
Arquivo:
import { Landing, Login, Signup } from "./screens";
Também:
<Route
path="/signup"
element={user?.email ? <Navigate to="/home" /> : <Signup />}
/>
<Route
path="/login"
element={user?.email ? <Navigate to="/home" /> : <Login />}
/>
Atualizando useFetch
A autenticação do Google retorna uma resposta com credenciais JWT. No entanto, para verificar sua autenticidade e também criar uma sessão para o usuário, faremos chamadas subsequentes ao servidor. Devemos atualizar nosso hooks/useFetch
arquivo para ficar assim:
const handleGoogle = async (response) => {
setLoading(true);
fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ credential: response.credential }),
})
.then((res) => {
setLoading(false);
return res.json();
})
.then((data) => {
if (data?.user) {
localStorage.setItem("user", JSON.stringify(data?.user));
window.location.reload();
}
throw new Error(data?.message || data);
})
.catch((error) => {
setError(error?.message);
});
};
Vamos dividir isso:
- Nossa função de retorno de chamada aceita um parâmetro da autenticação do Google passado como resposta.
- Usamos então
fetch
para fazer uma solicitação ao servidor. - Quando obtemos a resposta apropriada, armazenamos o usuário no
localStorage
em formato JSON.
Criando Rotas de Inscrição e Login
Abra o server.js
Arquivo. Antes de tudo, vamos criar uma função que verifica as credenciais que vamos receber:
const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID;
const client = new OAuth2Client(GOOGLE_CLIENT_ID);
async function verifyGoogleToken(token) {
try {
const ticket = await client.verifyIdToken({
idToken: token,
audience: GOOGLE_CLIENT_ID,
});
return { payload: ticket.getPayload() };
} catch (error) {
return { error: "Invalid user detected. Please try again" };
}
}
Crie um .env
arquivo na pasta raiz do servidor e adicione o seguinte:
# .env
GOOGLE_CLIENT_ID=your client id
JWT_SECRET=mySecret
Em seguida, crie a rota de inscrição:
app.post("/signup", async (req, res) => {
try {
if (req.body.credential) {
const verificationResponse = await verifyGoogleToken(req.body.credential);
if (verificationResponse.error) {
return res.status(400).json({
message: verificationResponse.error,
});
}
const profile = verificationResponse?.payload;
DB.push(profile);
res.status(201).json({
message: "Signup was successful",
user: {
firstName: profile?.given_name,
lastName: profile?.family_name,
picture: profile?.picture,
email: profile?.email,
token: jwt.sign({ email: profile?.email }, "myScret", {
expiresIn: "1d",
}),
},
});
}
} catch (error) {
res.status(500).json({
message: "An error occurred. Registration failed.",
});
}
});
Crie também a rota de login:
app.post("/login", async (req, res) => {
try {
if (req.body.credential) {
const verificationResponse = await verifyGoogleToken(req.body.credential);
if (verificationResponse.error) {
return res.status(400).json({
message: verificationResponse.error,
});
}
const profile = verificationResponse?.payload;
const existsInDB = DB.find((person) => person?.email === profile?.email);
if (!existsInDB) {
return res.status(400).json({
message: "You are not registered. Please sign up",
});
}
res.status(201).json({
message: "Login was successful",
user: {
firstName: profile?.given_name,
lastName: profile?.family_name,
picture: profile?.picture,
email: profile?.email,
token: jwt.sign({ email: profile?.email }, process.env.JWT_SECRET, {
expiresIn: "1d",
}),
},
});
}
} catch (error) {
res.status(500).json({
message: error?.message || error,
});
}
});
Vamos decompô-lo:
- Nas rotas, primeiro verificamos se as credenciais são passadas para o corpo. Em seguida, tentamos verificar a credencial. Se houver um erro, nós o enviamos de volta ao cliente no formato JSON.
- Na rota de inscrição, armazenamos os perfis dos usuários no array de banco de dados e enviamos uma resposta de sucesso com um e-mail assinado JWT como token.
- Na rota de login, verificamos se o usuário existe no banco de dados e, se não, lançamos um erro. Se existir, também enviamos uma resposta de sucesso com um e-mail assinado JWT como um token com outros parâmetros.
Atualizando App.js
No App.js
do aplicativo cliente, atualizaremos o arquivo para verificar se há um usuário no local storage
com o seguinte código:
useEffect(() => {
const theUser = localStorage.getItem("user");
if (theUser && !theUser.includes("undefined")) {
setUser(JSON.parse(theUser));
}
}, []);
Criando Home.jsx
o Home.jsx
file é a página que estará disponível para o usuário após uma inscrição ou login bem-sucedido:
import React from "react";
const Home = ({ user }) => {
const logout = () => {
localStorage.removeItem("user");
window.location.reload();
};
return (
<div style={{ textAlign: "center", margin: "3rem" }}>
<h1>Dear {user?.email}</h1>
<p>
You are viewing this page because you are logged in or you just signed
up
</p>
<div>
<button
onClick={logout}
style={{
color: "red",
border: "1px solid gray",
backgroundColor: "white",
padding: "0.5rem 1rem",
cursor: "pointer",
}}
>
Logout
</button>
</div>
</div>
);
};
export default Home;
Em seguida, vamos exportá-lo do screens/index.js
arquivo assim:
export { default as Home } from "./Home";
Depois disso, importaremos e configuraremos sua rota em App.js
:
import { Home, Landing, Login, Signup } from "./screens";
Também:
<Route
path="/home"
element={user?.email ? <Home user={user} /> : <Navigate to="https://www.sitepoint.com/" />}
/>
Conclusão
Parabéns! Configuramos a nova autenticação do Google.
Mais uma vez, o código fonte está disponível aqui: Servidor e Cliente.
Leitura relacionada:
[ad_2]
Source link