3 JsonWebToken¶
Introducción¶
Un JSON Web Token (JWT) es un estándar abierto (RFC 7519) que define un método compacto y autocontenido para la transmisión segura de información entre dos partes como un objeto JSON. Esta información puede ser verificada y ser fiable porque está firmada digitalmente. Entre sus usos, podemos encontrar:
- Autenticación: Se puede usar para autenticar usuarios en APIs y aplicaciones web.
- Autorización: Se puede usar para determinar los permisos de un usuario para acceder a recursos.
- Intercambio de datos: Se puede usar para compartir información de forma segura entre diferentes aplicaciones.
La firma digital protege la integridad del token y evita la falsificación. Por ello, es importante que la firma sea secreta de esta forma no hay manera de poder desintegrar el token.
Al usar el formato JSON nos permite una mayor compacidad ya que es ligero y eficiente. Además, nos permite almacenar cualquier tipo de información, por lo que nos permite una mayor flexibilidad.
Funcionamiento¶
Un JWT se compone de tres partes separadas por puntos (.):
- Cabecera (Header): Contiene información sobre el token, como el algoritmo de firma utilizado y el tipo de token.
- Carga útil (Payload): Contiene la información que se desea transmitir, como la identidad del usuario, roles, permisos y tiempo de expiración.
- Firma (Signature): Se utiliza para verificar la integridad del token y garantizar que no ha sido modificado. Es importante que para salvaguardar la integridad del token dicha firma sea secreta.
Para poder crear el token, en primer lugar, el emisor del token (por ejemplo, un servidor de autenticación) crea un objeto JSON con la información que desea transmitir. El objeto JSON es firmado digitalmente con un algoritmo específico (por ejemplo, RSA o HMAC) y se codifica en Base64. Luego, el token se envía al destinatario (por ejemplo, un cliente web o una aplicación móvil) y éste verifica la firma del token y decodifica la información contenida en él. Si la firma es válida, el destinatario puede usar la información del token para tomar decisiones sobre la solicitud del usuario.
Creación del token¶
Para poder crear el token, es necesario primero que el cliente realice un login con los datos de credenciales, luego con la información requerida crear el parseo del token. Para realizar todo este proceso, será necesario usar la librería jsonwebtoken
.
app.post('/login', async (request, response) => {
const { body } = request
const { username, password } = body
const user = await User.findOne({ username })
const passwordCorrect = user === null
? false
: await compare(password, user.passwordHash)
if (!(user && passwordCorrect)) {
response.status(401).json({
error: 'invalid user or password'
})
}
// Información para el token
const userForToken = {
id: user.id,
username: user.username
}
const token = jwt.sign(
userForToken,
process.env.SECRET,
{
expiresIn: 60 * 60 * 24 * 7
}
)
response.send({
name: user.name,
username: user.username,
token
})
})
Con el método sign
, podemos firmar la información y crear el token. Dicho método recibe el payload que quiere almacenar, y la firma del token. Además puede recibir un objeto con diferentes opciones, como por ejemplo:
- algorithms: permite indicar el algoritmo a usar para el cifrado.
- complete: retorna el objeto completo del token a la hora de verificar, con el header, payload y la firma, en lugar de solo el payload.
- expiresIn: indica un tiempo de expiración del token. Una vez pasado dicho el tiempo, el token no será válido.
Verificación del token¶
Para verificar el token, usamos el método verify
de la librería. Dicho método recibe el valor del token, y la firma secreta a usar. Podemos usar el token a la hora de realizar una petición, a través del header Authorization de tipo Bearer (el tipo de autorización permitida para las JWT) o como API Key. Lo recomendable, es crear un middleware que realice la verificación y devuelva a la siguiente operación el valor deseado a través de la petición.
export const userExtractor = (request, response, next) => {
const authorization = request.get('authorization') //(1)!
let token = ''
if (authorization && authorization.toLowerCase().startsWith('bearer')) { //(2)!
token = authorization.substring(7)
}
const decodedToken = jwt.verify(token, process.env.SECRET) //(3)!
if (!token || !decodedToken.id) { //(4)!
return response.status(401).json({ error: 'token missing or invalid' })
}
const { id: userId } = decodedToken //(5)!
request.userId = userId //(6)!
next()
}
- Se obtiene el header authorization
- Se comprueba que dicho header es de tipo bearer
- Se verifica el token con el método
verify
, devuelve un undefined si el token no es válido, por el contrario retorna el valor del payload. - Se obtiene los datos requeridos del payload.
- Se añade dichos datos a la request para pasarla a la siguiente petición con el método
next
Por último, podemos usar la información requerida, así como verificar ciertas operaciones, como por ejemplo, si el usuario es administrador, tiene los permisos suficientes, etc., de la siguiente forma:
app.post('/notes', userExtractor, async (request, response, next) => {
const {
content,
important = false
} = request.body
const { userId } = request
const user = await User.findById(userId)
if (!content) {
return response.status(400).json({
error: 'required "content" field is missing'
})
}
const newNote = new Note({
content,
date: new Date(),
important,
user: user.id
})
try {
const savedNote = await newNote.save()
user.notes = user.notes.concat(savedNote.id)
await user.save()
response.json({ ...savedNote })
} catch (error) {
next(error)
}
})