Cómo autenticarse mediante cookies GraphQL y JWT

 

 

 

Como autenticarse mediante cookies graphql y jwt 1

 

Un ejemplo de proceso de autenticación para una API GraphQL impulsada por Apollo, que utiliza cookies y JWT

En este tutorial se explicará cómo manejar un mecanismo de inicio de sesión para una API GraphQL usando Apollo.

Crearemos un área privada que dependiendo de tu inicio de sesión de usuario mostrará diferente información.

En detalle, estos son los pasos:

  • Crear un formulario de inicio de sesión en el cliente
  • Envía los datos de inicio de sesión al servidor
  • Autenticar al usuario y enviar un JWT de vuelta
  • Almacenar el JWT en una galleta
  • Utilice JWT para realizar más aplicaciones a la API GraphQL

El código para este tutorial está disponible en GitHub en https://github.com/flaviocopes/apollo-graphql-client-server-authentication-jwt

Vamos a empezar.

¡ADVERTENCIA! Este tutorial es antiguo. Apollo ahora usa @apollo/xxxen lugar de apollo-xxx. Investiga hasta que lo actualice

Inicio la aplicación cliente

Creemos la parte del lado del cliente usando create-react-app, ejecútelo npx create-react-app clienten una carpeta vacía.

Luego llama cd clienty npm installtodas las cosas que necesitaremos para no tener que volver más tarde:

npm install apollo-client apollo-boost apollo-link-http apollo-cache-inmemory react-apollo apollo-link-context @reach/router js-cookie graphql-tag

El formulario de inicio de sesión

Comenzamos a crear el formulario de inicio de sesión.

Crea un Form.jsarchivo en la srccarpeta y agrega este contenido:

import React, { useState } from 'react'import { navigate } from '@reach/router'const url = 'https://localhost:3000/login'const Form = () = { const [email, setEmail] = useState('') const [password, setPassword] = useState('') const submitForm = event = { event.preventDefault() const options = { method: 'post', headers: { 'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8' }, body: `email=${email}password=${password}` } fetch(url, options) .then(response = { if (!response.ok) { if (response.status === 404) { alert('Email not found, please retry') } if (response.status === 401) { alert('Email and password do not match, please retry') } } return response }) .then(response = response.json()) .then(data = { if (data.success) { document.cookie = 'token=' + data.token navigate('/private-area') } }) } return ( div form onSubmit={submitForm} pEmail: input type="text" onChange={event = setEmail(event.target.value)} //p pPassword: input type="password" onChange={event = setPassword(event.target.value)} //p pbutton type="submit"Login/button/p /form /div )}export default Form

Aquí supongo que el servidor se ejecutará localhosten el protocolo HTTP en el puerto 3000.

 

Utilizo React Hooks y Reach Router. No hay código Apolo aquí. Solo un formulario y algo de código para registrar una nueva cookie cuando nos autenticamos correctamente.

Al utilizar la API Fetch, cuando el usuario envía el formulario, me comunico con el servidor en el /loginpunto final REST con una solicitud POST.

Cuando el servidor confirme que hemos iniciado sesión, almacenará el token JWT en una cookie y navegará a la /private-areaURL, que aún no hemos creado.

Añade el formulario a la aplicación

Editemos el index.jsarchivo de la aplicación para usar este componente:

import React from 'react'import ReactDOM from 'react-dom'import { Router } from '@reach/router'import Form from './Form'ReactDOM.render( Router Form path="/" / /Router document.getElementById('root'))

El lado del servidor

Cambiamos del lado del servidor.

Crea una servercarpeta y ejecútala npm init -ypara crear un archivo listo para usar package.json.

Ahora corre

npm install express apollo-server-express cors bcrypt jsonwebtoken

A continuación, cree un archivo app.js.

Aquí primero vamos a manejar el proceso de inicio de sesión.

Vamos a crear algunos datos ficticios. Un usuario:

const users = [{ id: 1, name: 'Test user', email: '[email protected]', password: '$2b$10$ahs7h0hNH8ffAVg6PwgovO3AVzn1izNFHn.su9gcJnUWUzb2Rcb2W' // = ssseeeecrreeet}]

y algunos elementos TODO:

const todos = [ { id: 1, user: 1, name: 'Do something' }, { id: 2, user: 1, name: 'Do something else' }, { id: 3, user: 2, name: 'Remember the milk' }]

Los dos primeros elementos están asignados al usuario que acabamos de definir. El tercer elemento pertenece a otro usuario. Nuestro objetivo es iniciar sesión con el usuario y mostrar solo los elementos TODO que le pertenecen.

El hash de la contraseña, por ejemplo, lo generé yo manualmente usando bcrypt.hash()y corresponde a la ssseeeecrreeetcadena. Más información sobre bcrypt aquí. En la práctica, almacenarás usuarios y tareas pendientes en una base de datos, y los hashes de contraseñas se crean automáticamente cuando los usuarios se registran.

 

Manejar el proceso de inicio de sesión

Ahora quiero manejar el proceso de inicio de sesión.

Cargo un montón de bibliotecas que vamos a usar e inicializo Express para usar CORS , de modo que podemos usarlo desde nuestra aplicación cliente (ya que está en otro puerto), y agrega el middleware que analiza los datos codificados en URL:

const express = require('express')const cors = require('cors')const bcrypt = require('bcrypt')const jwt = require('jsonwebtoken')const app = express()app.use(cors())app.use(express.urlencoded({ extended: true}))

A continuación, defina lo que SECRET_KEYusaremos para la firma JWT y defina el /logincontrolador del punto final POST. Hay una asyncpalabra clave que usaremos awaiten el código. Extraigo los campos de correo electrónico y contraseña del cuerpo de la solicitud y busca al usuario en nuestra users“base de datos”.

Si no se encuentra al usuario por su correo electrónico, envío un mensaje de error.

A continuación, verifique si la contraseña no coincide con el hash que tenemos y, si es así, envío un mensaje de error.

Si todo va bien, genere el token mediante la jwt.sign()llamada, pasando el emaily idcomo datos de usuario, y lo envío al cliente como parte de la respuesta.

Aquí está el código:

const SECRET_KEY = 'secret!'app.post('/login', async (req, res) = { const { email, password } = req.body const theUser = users.find(user = user.email === email) if (!theUser) { res.status(404).send({ success: false, message: `Could not find account: ${email}`, }) return } const match = await bcrypt.compare(password, theUser.password) if (!match) { //return error to user to let them know the password is incorrect res.status(401).send({ success: false, message: 'Incorrect credentials', }) return } const token = jwt.sign( { email: theUser.email, id: theUser.id }, SECRET_KEY, ) res.send({ success: true, token: token, })})

Ahora puedo iniciar la aplicación Express:

app.listen(3000, () = console.log('Server listening on port 3000'))

El área privada

En este punto, del lado del cliente, agrega el token a las cookies y lo muevo a la /private-areaURL.

¿Qué hay en esa URL? ¡Nada! Agreguemos un componente para manejar eso, en src/PrivateArea.js:

import React from 'react'const PrivateArea = () = { return ( div Private area! /div )}export default PrivateArea

En index.js, podemos agregar esto a la aplicación:

import React from 'react'import ReactDOM from 'react-dom'import { Router } from '@reach/router'import Form from './Form'import PrivateArea from './PrivateArea'ReactDOM.render( Router Form path="/" / PrivateArea path="/private-area" / /Router document.getElementById('root'))

Utilizo la js-cookiebiblioteca Nice para trabajar fácilmente con cookies. Con ella compruebo si hay tokengalletas. Si no, solo tengo que volver al formulario de inicio de sesión:

 

import React from 'react'import Cookies from 'js-cookie'import { navigate } from '@reach/router'const PrivateArea = () = { if (!Cookies.get('token')) { navigate('/') } return ( div Private area! /div )}export default PrivateArea

En teoría, ya podemos empezar a usar la API GraphQL, pero todavía no tenemos nada parecido. Vamos a crearlo.

La API GraphQL

Del lado del servidor, lo hago todo en un solo archivo. No es tan grande, ya que tenemos pequeñas cosas en su lugar. Korean Beauty

Añado esto al principio del archivo:

const { ApolloServer, gql, AuthenticationError,} = require('apollo-server-express')

que nos da todo lo que necesitamos para crear el servidor GraphQL de Apollo.

Necesito definir 3 cosas:

  • El esquema GraphQL
  • resolutores
  • El contexto

Aquí está el esquema. Defina el Usertipo, que representa lo que tenemos en nuestro objeto de usuarios. Luego el Todotipo y, por último, el Querytipo, que establece lo que podemos consultar directamente: la lista de tareas pendientes.

const typeDefs = gql` type User { id: ID! email: String! name: String! password: String! } type Todo { id: ID! user: Int! name: String! } type Query { todos: [Todo] }`

El Querytipo tiene una entrada y necesitamos definir un solucionador para él. Aquí está:

const resolvers = { Query: { todos: (root, args) = { return todos.filter(todo = todo.user === id) } }}

Luego, el contexto, donde básicamente verificamos el token y generamos un error si no es válido, y obtenemos los valores idy emailde él. Así es como sabemos quién está hablando con la API:

const context = ({ req }) = { const token = req.headers.authorization || '' try { return { id, email } = jwt.verify(token.split(' ')[1], SECRET_KEY) } catch (e) { throw new AuthenticationError( 'Authentication token is invalid, please log in', ) }}

Los valores idy emailahora están disponibles dentro de nuestros solucionadores. De ahí idproviene el valor que usamos anteriormente.

¡Ahora necesitamos agregar Apollo a Express como middleware y la parte del lado del servidor está terminada!

const server = new ApolloServer({ typeDefs, resolvers, context })server.applyMiddleware({ app })

El cliente Apollo

¡Estamos listos para inicializar nuestro Cliente Apollo ahora!

En el index.jsarchivo del lado del cliente agregado esas bibliotecas:

import { ApolloClient } from 'apollo-client'import { createHttpLink } from 'apollo-link-http'import { InMemoryCache } from 'apollo-cache-inmemory'import { ApolloProvider } from 'react-apollo'import { setContext } from 'apollo-link-context'import { navigate } from '@reach/router'import Cookies from 'js-cookie'import gql from 'graphql-tag'

Inicializo un objeto HttpLink que apunta al servidor de API GraphQL, escuchando en el puerto 3000 del host local, en el /graphqlpunto final, y lo uso para configurar el ApolloClientobjeto.

 

Un HttpLink nos proporciona una forma de describir cómo queremos obtener el resultado de una operación GraphQL y qué queremos hacer con la respuesta.

const httpLink = createHttpLink({ uri: 'https://localhost:3000/graphql' })const authLink = setContext((_, { headers }) = { const token = Cookies.get('token') return { headers: { ...headers, authorization: `Bearer ${token}` } }})const client = new ApolloClient({ link: authLink.concat(httpLink), cache: new InMemoryCache()})

Si tenemos un token navego al área privada:

if (Cookies.get('token')) { navigate('/private-area')}

Y finalmente uso el ApolloProvidercomponente que importamos como componente padre y envuelvo todo en la aplicación que definimos. De esta manera podemos acceder al clientobjeto en cualquiera de nuestros componentes hijos. En particular, el de PrivateArea, ¡muy pronto!

ReactDOM.render( ApolloProvider client={client} Router Form path="/" / PrivateArea path="/private-area" / /Router /ApolloProvider, document.getElementById('root'))

El área privada

Así que llegamos al último paso. ¡Ahora por fin podemos ejecutar nuestra consulta GraphQL!

Esto es lo que tenemos ahora:

import React from 'react'import Cookies from 'js-cookie'import { navigate } from '@reach/router'const PrivateArea = () = { if (!Cookies.get('token')) { navigate('/') } return ( div Private area! /div )}export default PrivateArea

Voy a importar estos 2 elementos de Apollo:

import { gql } from 'apollo-boost'import { Query } from 'react-apollo'

y en lugar de

 return ( div Private area! /div )

Voy a utilizar el Querycomponente y pasar una consulta GraphQL. Dentro del cuerpo del componente pasamos una función que toma un objeto con 3 propiedades: loading, errory data.

Si bien los datos aún no están disponibles, loadinges cierto y podemos agregar un mensaje al usuario. Si hay algún error, lo recuperaremos, pero de lo contrario, obtendremos nuestros elementos de la tarea pendiente en el dataobjeto y podemos iterarlos para mostrar nuestros elementos al usuario.

 return ( div Query query={gql` { todos { id name } } `} {({ loading, error, data }) = { if (loading) return pLoading.../p if (error) { navigate('/') return p/p } return ul{data.todos.map(item = li key={item.id}{item.name}/li)}/ul }} /Query /div )

Ahora que todo está funcionando, quiero cambiar un poco el funcionamiento del código y agregar el uso de cookies HTTPOnly. Este tipo especial de cookie es más seguro porque no podemos acceder a ella mediante JavaScript y, como tal, no puede ser robada por scripts de terceros y utilizada como objetivo de ataques.

Las cosas son un poco más complejas ahora, así que agregué esto en la parte inferior.

 

Todo el código está disponible en GitHub en https://github.com/flaviocopes/apollo-graphql-client-server-authentication-jwt y todo lo que describí hasta ahora está disponible en este commit.

El código para esta última parte está disponible en este commit separado.

Primero, en el cliente, en Form.js, en lugar de agregar el token a una cookie, agrego una signedincookie.

Eliminar esto

document.cookie = 'token=' + data.token

y añadir

document.cookie = 'signedin=true'

A continuación en las fetchopciones debemos agregar

credentials: 'include'

De lo contrario, fetchno almacenará en el navegador las cookies que recibe del servidor.

Ahora en el PrivateArea.jsarchivo no buscamos la cookie del token, sino la signedincookie:

Eliminar

if (!Cookies.get('token')) {

y añadir

if (!Cookies.get('signedin')) {

Vayamos a la parte del servidor.

Primero instale la cookie-parserbiblioteca con npm install cookie-parsery en lugar de enviar de vuelta el token al cliente:

res.send({ success: true, token: token,})

Solo envía esto:

res.send({ success: true})

Enviamos el token JWT al usuario como una cookie HTTPOnly:

res.cookie('jwt', token, { httpOnly: true //secure: true, //on HTTPS //domain: 'example.com', //set your domain})

(en producción configure la opción segura en HTTPS y también el dominio)

A continuación, debemos configurar el middleware CORS para que también utilice cookies. De lo contrario, las cosas se estropearán muy pronto cuando gestionemos los datos de GraphQL, ya que las cookies simplemente desaparecerán.

Cambiar

app.use(cors())

con

const corsOptions = { origin: 'https://localhost:3001', //change with your own client URL credentials: true}app.use(cors(corsOptions))app.use(cookieParser())

Volviendo al cliente, le index.jsindicamos a Apollo Client que incluya credenciales (cookies) en sus solicitudes. Cambiar:

const httpLink = createHttpLink({ uri: 'https://localhost:3000/graphql' })

con

const httpLink = createHttpLink({ uri: 'https://localhost:3000/graphql', credentials: 'include' })

y eliminar la authLinkdefinición por completo:

const authLink = setContext((_, { headers }) = { const token = Cookies.get('token') return { headers: { ...headers, authorization: `Bearer ${token}` } }})

Como ya no lo necesitamos, simplemente lo pasaremos httpLinka new ApolloClient(), ya que no necesitamos más elementos de autenticación personalizados:

const client = new ApolloClient({ link: httpLink, cache: new InMemoryCache()})

¡De vuelta al servidor para la última pieza del rompecabezas! Abra index.jsy en la contextdefinición de la función, cambie

const token = req.headers.authorization || ''

estafa

const token = req.cookies['jwt'] || ''

y deshabilitar el manejo de CORS integrado de Apollo Server, ya que sobrescribe el que ya hicimos en Express, cambie:

const server = new ApolloServer({ typeDefs, resolvers, context })server.applyMiddleware({ app })

a

const server = new ApolloServer({ typeDefs, resolvers, context, cors: false })server.applyMiddleware({ app, cors: false })



Tal vez te puede interesar:

  1. Introducción a React
  2. Agregar evento de clic a los elementos DOM devueltos desde querySelectorAll
  3. Cómo cambiar el valor de un nodo DOM
  4. Cómo comprobar si un elemento DOM tiene una clase

Cómo autenticarse mediante cookies GraphQL y JWT

Inicio la aplicación clienteEl formulario de inicio de sesiónAñade el formulario a la aplicaciónEl lado del servidorManejar el proceso de inicio de sesión

programar

es

https://aprendeprogramando.es/static/images/programar-como-autenticarse-mediante-cookies-graphql-y-jwt-2143-0.jpg

2024-11-02

 

Como autenticarse mediante cookies graphql y jwt 1
Como autenticarse mediante cookies graphql y jwt 1

Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente

 

 

Update cookies preferences