Seguridad y Autenticación en aplicaciones web
Programación con Herramientas Modernas
Antes de arrancar
Verificá que tengas activada la opción “Disable cache” en el navegador (F12 > solapa Network)
Conceptos básicos de seguridad
Vamos a conocer algunos conceptos antes de meternos en el módulo de seguridad que propone Springboot.
Autenticación
La autenticación es el proceso de identificación de un usuario o proceso para acceder a un sistema.
Autenticación
Algunas formas de autenticación
datos biométricos
con tokens
usuario/password
con API Keys
Autorización
La autorización consiste en entender a qué recursos tiene acceso un usuario. Por ejemplo, un docente puede cargar notas de todos sus alumnes, pero une alumne solo puede ver sus notas.
Vulnerabilidades
Las vulnerabilidades son debilidades existentes en un sistema que pueden ser comprometer la seguridad de la información de una empresa. Parte del daño que pueden causar van desde la filtración o robo de datos, caída de la aplicación, pérdida de la confianza de los clientes, etc.
OWASP y el top 10 de vulnerabilidades
Existe un proyecto open-source llamado OWASP (Open Web Application Security Project), que se dedica a analizar y combatir las causas de que un software sea inseguro. Cada cierta cantidad de años OWASP lanza un reporte del top 10 de vulnerabilidades (en breve saldrá el reporte del 2025):
Un ejemplo: Cross Site Request Forgery (CSRF)
Supongamos que interactuamos con una app. En el login pedimos el formulario HTTP para ingresar nuestras credenciales.
GET /login
HTML 200 Form
CSRF
Eso permite que la próxima llamada nosotros pasemos esa cookie con la cual el servidor sabe que se trata de una sesión válida.
POST /auth
{ usuario: ‘juan’, password: ‘cito’ }
sessionId: #hash
CSRF
Por ejemplo, en una aplicación bancaria podríamos tener un formulario para transferir dinero a una cuenta mediante un request POST con la información accountNumber y amount. Junto con esos valores, nosotros transferimos también la cookie con el sessionId.
POST /transfer
{ accountNumber: 123, amount: 100 }
200 OK
El sessionId corresponde a un usuario válido
CSRF
Si activás las Herramientas del Navegador, vas a poder ver las cookies en la solapa Application (en Chrome) o Storage (en Firefox).
CSRF
Ahora bien, qué pasa si recibimos un mail del tipo “Su cuenta será bloqueada próximamente” con un link.
Mail con un link
La cookie sigue activa porque nos logueamos previamente
CSRF
Ahora bien, qué pasa si recibimos un mail del tipo “Su cuenta será bloqueada próximamente” con un link.
Mail con un link
Hacemos click en el link, que nos lleva al sitio del hacker
CSRF
Ahora bien, qué pasa si recibimos un mail del tipo “Su cuenta será bloqueada próximamente” con un link.
Mail con un link
200 OK, con un formulario que tiene campos ocultos (o directamente código JS)
CSRF
Ahora bien, qué pasa si recibimos un mail del tipo “Su cuenta será bloqueada próximamente” con un link.
Mail con un link
Banco Alas
Reactivar cuenta
CSRF
Al presionar en el botón “reactivar cuenta” lo que hacemos en el cliente es llamar a la app de Home Banking transfiriendo el account number del hacker. En cada request http las cookies se transfieren automáticamente, lo cual en este caso es malo.
Mail con un link
Reactivar cuenta
POST /transfer
{ accountNumber: 9999, amount: 100 }
9999 es la cuenta del hacker
CSRF
Por eso las cookies expiran, pero mientras esté activa hay altas chances de que el atacante tenga éxito.
200 OK
CSRF
Te dejamos dos videos que explican en español los conceptos detrás de CSRF.
CSRF > Synchronizer Token Pattern
Existen varios mecanismos para evitar Cross Site Request Forgery. El primero que veremos es el Synchronizer Token Pattern.
CSRF > Synchronizer Token Pattern
Con cada request el servidor genera un CSRF Token, otro hash que viaja al cliente y que puede renovarse con cada request1. La diferencia es que no la guardamos en una cookie, sino que la recibimos y la asociamos a cualquier formulario en un input hidden.
POST /auth
{ usuario: ‘juan’, password: ‘cito’ }
sessionId: #hash
csrf token: #otroHash
1 Eso de yapa evita submitir dos veces un mismo form (evita así que compres 2 veces)
CSRF > Synchronizer Token Pattern
Al definir <input type="hidden" name="_csrf" value="..."> cuando hacemos SUBMIT en el formulario enviamos ese token. Para eso es importante además respetar la convención REST: los métodos http GET, OPTIONS, HEAD no deben producir efecto (como transferir de una cuenta a otra).
POST /transfer
{ accountNumber: 123, amount: 100,
_csrf: #otroHash }
200 OK
CSRF > Synchronizer Token Pattern
Qué pasa si recibimos un mail del tipo “Su cuenta será bloqueada próximamente” con un link: el hacker puede pasarnos un formulario con sus datos...
Mail con un link
La cookie sigue activa porque nos logueamos previamente, pero no tenemos el _csrf
CSRF > Synchronizer Token Pattern
Al hacer click en “Reactivar cuenta” se hace submit sobre el formulario, pero no tiene el valor del _csrf porque es propio de cada usuario: el hacker solo puede obtener un _csrf si él hace una petición con su usuario al banco (cosa que no le sirve para lograr que otra persona le transfiera a su cuenta)
Mail con un link
Reactivar cuenta
POST /transfer
{ accountNumber: 9999, amount: 100 }
CSRF > Synchronizer Token Pattern
Si no se envía el _csrf, al enviar el POST el server verifica que el token sea válido y lo rechaza.
Mail con un link
401 Unauthorized
CSRF > Same site attribute
Otra alternativa para evitar el Cross Site Request Forgery (CSRF) es utilizar la estrategia Same site attribute (definido en RFC6265bis).
El server envía con la respuesta una configuración para la cookie llamada SameSite
GET /login
HTML 200 Form
Set-Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly; SameSite=<value>
servidor A
CSRF > Same site attribute
¿Cuando dos URLs comparten el mismo same site?
Cuando coinciden en el protocolo (http / https)
Cuando tienen el mismo dominio (unsam.edu.ar, youtube.com, etc.)
No entra en juego aquí el puerto, ni el subdominio (www)
servidor A
servidor B
CSRF > Same site attribute Strict
Las configuraciones posibles son: strict, en ese caso ninguna petición a otro servidor envía la cookie del servidor A.
HTML 200 Form
Set-Cookie: JSESSIONID=randomid; Domain=phm.edu.ar; Secure; HttpOnly; SameSite=Strict
servidor A
http://phm.edu.ar
no se envía la cookie al servidor B si el dominio no coincide con el servidor A
GET http://imagenesCopadas.com/dodain.png
CSRF > Same site attribute Lax
Otra opción es lax, en ese caso mandaremos la cookie si cambiamos la navegación top-level (desde el navegador, haciendo un redirect) o bien si utilizamos un método HTTP seguro (GET, HEAD pero no POST)
HTML 200 Form
Set-Cookie: JSESSIONID=randomid; Domain=phm.edu.ar; Secure; HttpOnly; SameSite=Lax
servidor A
http://phm.edu.ar
no se envía la cookie porque el dominio del servidor B no coincide con el servidor A y no es una navegación
img src= "http://imagenesCopadas.com/dodain.png"
CSRF > Same site attribute Lax
Otra opción es lax, en ese caso mandaremos la cookie si cambiamos la navegación top-level (desde el navegador, haciendo un redirect) o bien si utilizamos un método HTTP seguro (GET, HEAD pero no POST)
HTML 200 Form
Set-Cookie: JSESSIONID=randomid; Domain=phm.edu.ar; Secure; HttpOnly; SameSite=Lax
servidor A
http://phm.edu.ar
se envía la cookie si nosotros ponemos un link que redirige la url del navegador y es un método GET (es top level navigation)
<a href= "http://imagenesCopadas.com/dodain.png">
Ver foto</a>
CSRF > Same site attribute Lax
Si no definís en tu server una configuración para Same Site attribute, por defecto los navegadores utilizan la variante Lax.
La excepción es Safari que permite que lo configures vos.
CSRF > Same site attribute None
La última configuración es None, que básicamente envía siempre las cookies, no importa si los sites son iguales o no, o el método http que utilizamos para acceder al recurso.
HTML 200 Form
Set-Cookie: JSESSIONID=randomid; Domain=phm.edu.ar; Secure; HttpOnly; SameSite=None
servidor A
http://phm.edu.ar
siempre enviamos la cookie
cualquier operación
CSRF > Same site attribute
Te dejamos un video que si bien está en inglés es muy claro explicando cómo funcionan cada una de las configuraciones de Same Site
XSS: Cross Site Scripting
El cross site scripting es parecido al CSRF pero en este caso
XSS: Cross Site Scripting
¿Pero qué tan frecuente es?
En 2017 era la séptima causa de vulnerabilidad.
El tweet que está a la derecha generó un ataque XSS para retweetearse cada vez que se mostraba.
el tweet que generó el exploit
Stored XSS
function handleMessageSend(messageId, senderEmail, messageContent) {
database.save(messageId, senderEmail, messageContent)
}
function generateMessageHTML(messageId) {
const messageContent = database.loadContent(messageId);
return `<p class="messageContent">${messageContent}</p>`
}
<script>alert(’Hackeado!’)</script>
Reflected XSS
app.get("/results", (req, res) => {
const searchQuery = req.query.search;
const results = getResults(searchQuery); // Implementation not shown
res.send(`
<h1>You searched for ${searchQuery}</h1>
<p>Here are the results: ${results}</p>`);
});
Código en el server - Express
DOM-based XSS
Cómo mitigar XSS
Cómo mitigar XSS
Content-Type: application/json;charset=UTF-8
permite que el navegador desactive Javascript en el cliente.
{"name":"<script>alert(1)</script>","title":"bar"}
Recibir como respuesta este JSON en nuestra app cliente no ejecutará el alert en el navegador si definimos que el content type es JSON.
Cómo mitigar XSS
La última medida de defensa es activar el Content Security Policy (CSP). Podemos definir mediante el http header qué recursos podemos descargar en el cliente con seguridad. Veamos un ejemplo:
Aquí vemos que solo podemos descargar código js si el cliente y el server coinciden con el mismo sitio, y podemos descargar imágenes del mismo sitio o bien de www.example.com
Spring Security
Spring Security es la propuesta de Springboot para manejar la seguridad en lo que es
Spring Security
Spring Security es la propuesta de Springboot para manejar la seguridad en lo que es
Spring Security
Spring Security es la propuesta de Springboot para manejar la seguridad en lo que es
Spring Security
Spring Security es la propuesta de Springboot para manejar la seguridad en lo que es
Spring Security: Autenticación y Roles
Vamos a definir qué endpoints deben estar autenticados, y qué roles deben tener acceso a cada caso de uso
Usuarios admin: pueden ver y actualizar información
Usuarios comunes: solo pueden ver información
Usuarios sin autenticar: solo pueden intentar loguearse o ver una página con errores
Spring Security: Autenticación
Vamos a definir qué endpoints deben estar autenticados, y qué roles deben tener acceso a cada caso de uso
@Bean
fun securityFilterChain(httpSecurity: HttpSecurity): SecurityFilterChain {
return httpSecurity...
.authorizeHttpRequests {
it.requestMatchers("/login").permitAll()
it.requestMatchers("/error").permitAll()
it.requestMatchers(HttpMethod.OPTIONS).permitAll()
// Permisos de admin para modificar
it.requestMatchers(HttpMethod.POST, "/heladerias").hasAuthority("ADMIN")
it.requestMatchers(HttpMethod.POST, "/duenios").hasAuthority("ADMIN")
it.requestMatchers(HttpMethod.PUT, "/heladerias/**").hasAuthority("ADMIN")
// Default: que se autentique para poder ver información
.anyRequest().authenticated()
}
Spring Security: Autenticación
¿Dónde defino los usuarios y los roles?
Keycloak: provee un mecanismo de Single Sign On (SSO, o login unificado), federación de usuarios (compartir un usuario entre varios servers)
Spring Security: Autenticación
¿Dónde defino los usuarios y los roles?
OAuth2: te podés integrar con servicios de autenticación externos, como tu correo de Google, Facebook, Github, etc.
Spring Security: Autenticación
¿Dónde defino los usuarios y los roles?
InMemoryUserDetailsManager: genera una base de usuarios en memoria cada vez que se levanta el servidor
Spring Security: Autenticación
¿Dónde defino los usuarios y los roles?
O creamos nuestras tablas de Usuarios y Roles y los vamos a integrar con Spring Security.
Spring Security: Autenticación
De hecho... en lugar de "ADMIN" vamos a usar ROLES.ADMIN.name para trabajar los roles como wko (objetos bien conocidos), implementados como enums.
Spring Security: Autenticación
Un dato importante, la password se encripta en la base. No es 100% seguro pero es mejor que tenerlo libre...
class Usuario {
private var password: String = ""
private fun getDefaultEncoder(): PasswordEncoder =
Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8()!!
fun crearPassword(rawPassword: String) { password = getDefaultEncoder().encode(rawPassword) }
Spring Security: Autenticación
Una vez que validamos usuario y password, queremos que la comunicación entre cliente y servidor fluya. Para eso vamos a usar una firma digital: un token, que no es otra cosa que una serie de bytes.
Springboot: Autenticación con JWT
Presentamos a JWT: JSON Web Token, donde el token representa
Podemos ver https://jwt.io/
Springboot: Autenticación con JWT
En la autenticación por JWT el usuario y la contraseña se validan y lo que vuelve es un token, que hay que utilizar en cada una de las llamadas siguientes (recordemos que http es stateless).
Springboot: Autenticación con JWT
Vemos la implementación en Springboot:
@Bean
fun securityFilterChain(httpSecurity: HttpSecurity): SecurityFilterChain {
return httpSecurity
...
.httpBasic(Customizer.withDefaults())
.sessionManagement { configurer ->
configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
// definimos aquí que cada request tendrá JWT para identificar quién lo envía
.addFilterBefore(jwtAuthorizationFilter, UsernamePasswordAuthenticationFilter::class.java)
httpBasic permite capturar el header `Authorization` de cada request con el Bearer Token
Luego de analizar el login veremos qué hace este filter (decorator)
Springboot: Autenticación con JWT
El endpoint de login no tiene autenticación, delega la validación al service y devuelve el token:
Pueden ver cómo la implementación del service delega la búsqueda al repositorio y cierta lógica al dominio (como verificar la contraseña en base al algoritmo de encriptación)
@PostMapping("/login")
fun login(@RequestBody credencialesDTO: CredencialesDTO): String {
return usuarioService.login(credencialesDTO)
}
@Transactional(Transactional.TxType.NEVER)
fun login(credencialesDTO: CredencialesDTO): String {
val usuario = validarUsuario(credencialesDTO.usuario)
usuario.validarCredenciales(credencialesDTO.password)
return tokenUtils.createToken(credencialesDTO.usuario,
usuario.roles.map { it.name })!!
}
Springboot: Autenticación con JWT
El algoritmo que genera el token es
es importante especificar un vencimiento para el token (si te hackean es limitado el daño que puede causar
fun createToken(nombre: String, roles: List<String>): String? {
val longExpirationTime = accessTokenMinutes.minutes.inWholeMilliseconds
val now = Date()
return Jwts.builder()
.subject(nombre)
.issuedAt(now)
.expiration(Date(now.time + longExpirationTime))
.claim("roles", roles)
.signWith(Keys.hmacShaKeyFor(secretKey.toByteArray()))
.compact()
}
Springboot: Autenticación con JWT
Configuraciones como el vencimiento del token o el secret key se definen como autowired:
Eso se configura en el application.yml, o bien en un application-dev.yml que no subimos a git (al menos el secret):
@Component
class TokenUtils {
@Value("\${security.secret-key}")
lateinit var secretKey: String
@Value("\${security.access-token-minutes}")
var accessTokenMinutes: Int = 60
security:
secret-key: ...
access-token-minutes: 300
JWT: Login desde Bruno
El login nos devuelve un código http 401 si las credenciales no son válidas (“credenciales inválidas”, sin especificar demasiado) o el token correspondiente:
JWT: Login desde Bruno
Ese token se almacena como variable Post Response...
JWT: Login desde Bruno
...y se define como Header en otros endpoints:
JWT: Login desde POSTMAN
Configuramos un script post-response:
pm.environment.set("token", pm.response.text())
JWT: Login desde POSTMAN
...y se define como Bearer Token en otros endpoints:
JWT: Login desde Insomnia
Vemos cómo podemos configurar las llamadas a otros endpoints desde Insomnia tomando como input el token que recibimos en el login:
Springboot: Autenticación con JWT
El JWTAuthorizationFilter decora todos los request que reciben el header Authorization para procesar el token y asignarle el usuario/rol correspondiente...
override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) {
val bearerToken = request.getHeader("Authorization")
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
val token = bearerToken.substringAfter("Bearer ")
val usernamePAT = tokenUtils.getAuthentication(token)
usuarioService.validarUsuario(usernamePAT.name)
SecurityContextHolder.getContext().authentication = usernamePAT
Login: Qué pasa en el frontend
En el login vamos a recibir el token, lo almacenamos en el LocalStorage del navegador:
Login: Qué pasa en el frontend
Vemos el código que resuelve esta parte, en el service que maneja el usuario:
export async function loginUser(usuario: string, password: string) {
const token = await httpRequest<string>({
method: 'POST',
url: `${BACKEND_URL}/login`,
data: { usuario, password }
})
localStorage.setItem(TOKEN_KEY, token)
}
Login: Qué pasa en el frontend
Las alternativas para almacenar el token son: guardarlo en el Local Storage (eso persiste aun cuando cerramos el navegador), o bien en el Session Storage (está atado a la sesión, al cerrar el navegador se borra la información), o guardarlo en una cookie, técnica que usaremos más adelante para evitar CSRF. Podés profundizar en este link.
Login: salir de la aplicación
Tenemos un botón que borra el token manualmente:
Login: salir de la aplicación
El código es bastante sencillo, eliminamos el token y redirigimos al login:
const logout = () => {
localStorage.removeItem(TOKEN_KEY)
navigate({
to: '/login',
search: {
redirect: '/',
},
})
}
token
Login: sesión vencida (backend)
Cada vez que hacemos un pedido, el server valida que el token no haya expirado. Si el token se vence atrapamos la excepción para que el mecanismo de Springboot no emita un error http 500: por más que asociemos el código 401 a TokenExpiradoException, necesitamos explícitamente setear el error de la respuesta.
} catch (e: TokenExpiradoException) {
logger.warn(e.message)
response.sendError(HttpStatus.UNAUTHORIZED.value(), e.message)
}
Login: sesión vencida (frontend)
...ese 401 lo recibimos como error en el service. Dentro del mecanismo de routing nosotros definimos una ruta específica cuando ocurre un error:
export const Route = createRootRoute({
component: RootComponent, // el componente principal
errorComponent: RouteErrorComponent,
})
Login: sesión vencida (frontend)
Ese componente llama a un modal (también llamada ventana de diálogo, un Toast un poco más elaborado), que se activa como un contenedor por encima de la página actual para mostrar un mensaje de error:
const RouteErrorComponent = ({ error, reset }: ErrorComponentProps) => {
const router = useRouter()
const onRetry = async () => {
await router.invalidate()
reset()
}
return <ErrorModal error={error as AxiosError} title={'Ups! Hubo...'} onRetry={onRetry} />
}
export default RouteErrorComponent
Login: sesión vencida (frontend)
El modal muestra el error y redirige al login:
const errorMessage = sessionExpired
? 'La sesión ha expirado. Por favor, inicie sesión nuevamente.' : ...
return (
<Modal ... isOpened={!!error} close={onClose}>
<ModalResponseContent ... <p>{errorMessage}</p>
actions={[
sessionExpired && (
<Button label='Navegar a login'
key='goToLogin' type='button' onClick={logout} />
), ...
Login: sesión vencida (frontend)
Además, en cada ruta tenemos una función general para manejar errores. Allí preguntamos si venció la sesión y borramos el token para evitar mandarlo la próxima vez...
// errors.ts
export const isSessionExpired =
(error: AxiosError) => error.status === HttpStatusCodes.UNAUTHORIZED
// routes.ts
export const onErrorRoute = (error: AxiosError) => {
if (isSessionExpired(error)) {
localStorage.removeItem(TOKEN_KEY)
}
}
Login: roles (app)
Si intentamos utilizar el usuario phm para actualizar la heladería, vemos que no tenemos permisos suficientes:
Login: roles (backend)
Recordemos que al desencriptar el token podemos saber qué usuario somos, y por lo tanto qué rol:
// TokenUtils.kt
fun getAuthentication(token: String): UsernamePasswordAuthenticationToken {
try {
val secret = Keys.hmacShaKeyFor(secretKey.toByteArray())
val claims = Jwts.parser().verifyWith(secret).build()
.parseSignedClaims(token).payload
...
val roles = (claims["roles"] as List<*>).map { SimpleGrantedAuthority(it.toString()) }
return UsernamePasswordAuthenticationToken(claims.subject, null, roles)
Login: roles (backend)
Y también recordemos que en la configuración de Spring Security definimos qué roles pueden acceder a qué endpoints. El de la actualización de la heladería solo está permitido para ADMIN:
Lo bueno es que no necesitamos hacer nada en nuestro endpoint, la configuración intercepta la llamada y devuelve un código http 403 (forbidden)...
.authorizeHttpRequests {
...
it.requestMatchers(HttpMethod.PUT, "/heladerias/**").hasAuthority(ROLES.ADMIN.name)
Login: roles (frontend)
...ese 403 lo recibimos como error en el service y se activa nuevamente la ventana modal (es el mismo código que con el vencimiento del token pero con un mensaje diferente).
Spring Security - CORS
Un cliente puede pedir al servidor imágenes, css, código javascript, videos, entre otras cosas
Cliente Web
Servidor Web
Spring Security - CORS
Pero para hacer pedidos mediante fetch o axios, utilizamos de fondo el objeto XMLHttpRequest, que requiere un mecanismo de negociación entre cliente y servidor, donde el servidor nos tiene que indicar qué orígenes están autorizados a hacer pedidos (requests). Ése es básicamente el mecanismo CORS
Cliente Web
Servidor Web
XMLHttpRequest
fetch/axios
Spring Security - CORS
La mayoría de los navegadores hacen una solicitud preflight a través de un método http OPTIONS para saber si es un origen válido para hacer el pedido correspondiente (que será GET, POST, etc.) ...
Cliente Web
Servidor Web
preflight / OPTIONS
Spring Security - CORS
el server le responde con headers sobre los orígenes válidos (p. ej: “https://elfront.com” o “*” que indica que acepta cualquier origen) y métodos http aceptados (p. ej: podría aceptar POST o GET pero no PUT).
Cliente Web
Servidor Web
headers
Spring Security - CORS
En resumen, el preflight devuelve 200 (todo ok) o un código de error (no tenés permisos para hacer este pedido 403 o 204, No Content).
Spring Security - CORS
Y todas las veces que hacemos un request, los navegadores hacen la negociación:
Spring Security - CORS
Vemos que a cada método le precede una llamada a OPTIONS (siempre tener en cuenta la opción “Disable cache”)
Spring Security - CORS
Desactivamos aquí CORS...
@Bean
fun securityFilterChain(httpSecurity: HttpSecurity): SecurityFilterChain {
return httpSecurity
.cors { it.disable() }
Spring Security - CORS
...porque luego le activamos esta configuración:
@Bean
fun corsConfigurer(): WebMvcConfigurer {
return object : WebMvcConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:5173")
.allowedHeaders("*")
.allowedMethods("POST", "GET", "PUT", "DELETE")
.allowCredentials(true)
}
}
}
queremos que el front sea reconocido (modo dev local)
y qué métodos http vamos a usar
CSRF: Backend
Una medida extra de seguridad que vamos a agregar es la protección contra CSRF, donde el server pasará como respuesta una cookie con un token: el token sirve únicamente para el usuario logueado (no sirve para hacer pedidos desde otro sitio).
@Bean
fun securityFilterChain(httpSecurity: HttpSecurity): SecurityFilterChain {
return httpSecurity
...
.csrf {
it.ignoringRequestMatchers("/login")
it.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
it.csrfTokenRequestHandler(SpaCsrfTokenRequestHandler())
el login no requiere token
el token lo pasamos a través de una cookie, httpOnly = false para que el frontend nos la pueda mandar
CSRF: Backend
Para manipular el token XSRF vamos a configurarle un handler específico. La implementación podés verla en el repositorio, lo importante es que podemos recibir el token
Solo se chequea el token en operaciones con efecto (PUT, POST, etc.)
CSRF: frontend
Axios ya trae una configuración para recibir la cookie con la clave XSRF-TOKEN y enviar el header con la clave X-XSRF-TOKEN:
export async function httpRequest<T>(request: AxiosRequestConfig): Promise<T> {
...
const okRequest = {
...request,
withXSRFToken: true,
withCredentials: true,
xsrfHeaderName: 'X-XSRF-TOKEN',
xsrfCookieName: 'XSRF-TOKEN',
}
const response = await axios(okRequest)
return response.data
}
CSRF: interacción
Vemos cómo funciona el mecanismo:
Analizadores de vulnerabilidades
¡Gracias!