que - Laravel 5.6-¿Autenticación SPA de pasaporte JWT httponly cookie para una API autoconsumente?
csrf token php (4)
NOTA: Esta es mi tercera recompensa en esta pregunta. Por favor, vota si estás interesado en una solución también.
He investigado mucho, pero la información siempre fue breve y no estaba completa cuando se trata de usar Laravel con una cookie JWT httponly para una API autoconsumente (la mayoría de los tutoriales en línea solo muestran que JWT se almacena en un almacenamiento local, lo cual no es muy seguro ). Parece que la cookie httponly que contiene un JWT by Passport debe usarse para identificar al usuario en el lado de Javascript cuando se envía con cada solicitud al servidor para validar que el usuario es quien dice ser.
También hay algunas cosas adicionales que se necesitan para tener una imagen completa de cómo hacer que esta configuración funcione, que no he encontrado en un solo tutorial que cubra esto:
- Laravel Passport (no es tymon auth) para generar JWT cifrado y enviarlo como cookie httponly como respuesta después de iniciar sesión desde el lado JS. ¿Qué middleware usar? Si los tokens de actualización añaden más seguridad, ¿cómo implementarlos?
- Pseudocódigo de api de JavaScript (por ejemplo, axios) que realiza la llamada al punto final de autenticación, cómo se pasa la cookie httponly al backend y cómo el token de verificación del backend es válido.
- Si una cuenta única ha iniciado sesión desde varios dispositivos, entonces se roba un dispositivo, ¿cómo revocar el acceso de todos los dispositivos de usuarios autenticados (suponiendo que el usuario cambie la contraseña de un dispositivo registrado en el que tiene control)?
- ¿Cómo se verían los métodos de inicio de sesión / registro, cierre de sesión, cambio de contraseña, contraseña olvidada para manejar la creación / validación / revocación de tokens?
- Integración de tokens CSRF.
Espero que una respuesta a esta pregunta sirva como una guía fácil de seguir para los futuros lectores y para aquellos que están luchando en este momento para encontrar una respuesta que cubra los puntos anteriores en una API autolítica.
ACTUALIZACIÓN 1:
- Tenga en cuenta que probé el
CreateFreshApiToken
antes, pero no funcionó cuando se trata de revocar tokens del usuario (para los puntos 3 y 4 anteriores). Esto se basa en este comentario de un desarrollador de laravel central, cuando se habla del middlewareCreateFreshApiToken
:
Los tokens JWT creados por este middleware no se almacenan en ninguna parte. No pueden ser revocados o "no existen". Simplemente brindan una forma para que sus llamadas a la API sean autenticadas a través de la cookie laravel_token. No está relacionado con los tokens de acceso. Además: normalmente no utilizaría tokens emitidos por clientes en la misma aplicación que los emite. Los utilizarías en una aplicación de primera o tercera parte. Utilice el middleware o los tokens emitidos por el cliente, pero no ambos al mismo tiempo.
Por lo que parece ser capaz de atender los puntos 3 y 4 para revocar tokens, no es posible hacerlo si se utiliza el middleware CreateFreshApiToken
.
- En el lado del cliente, parece que
Authorization: Bearer <token>
no es el camino a seguir cuando se trata con la cookie httpOnly segura. Creo que se supone que la solicitud / respuesta debe incluir la cookie httpOnly segura como un encabezado de solicitud / respuesta, como este basado en los documentos de laravel:
Cuando se utiliza este método de autenticación, el andamio predeterminado de Laravel JavaScript le indica a Axios que siempre envíe los encabezados X-CSRF-TOKEN y X-Requested-With.
headerswindow.axios.defaults.headers.common = {
''X-Requested-With'': ''XMLHttpRequest'',
''X-CSRF-TOKEN'': (csrf_token goes here)
};
Esta es también la razón por la que estoy buscando una solución que cubra todos los puntos anteriores. Disculpas, estoy usando Laravel 5.6 no 5.5.
ACTUALIZACIÓN 2:
Parece que la combinación de Contraseña Otorgar / Actualizar Token Oreja es el camino a seguir. Busca una guía de implementación fácil de usar con el combo de concesión de contraseña / renovación de token .
Contraseña de concesión: esta concesión es adecuada para tratar con el cliente en el que confiamos, como una aplicación móvil para nuestro propio sitio web. En este caso, el cliente envía las credenciales de inicio de sesión del usuario al servidor de autorización y el servidor emite directamente el token de acceso.
Actualizar concesión de token: cuando el servidor emite un token de acceso, también establece una caducidad para el token de acceso. La concesión de token de actualización se utiliza cuando queremos actualizar el token de acceso una vez que ha caducado. En este caso, el servidor de autorización enviará un token de actualización mientras emite el token de acceso, que se puede usar para solicitar un token de acceso nuevo.
Estoy buscando una respuesta holística y fácil de implementar utilizando el combo de Otorgar Contraseña / Actualizar Token Grant que cubre todas las partes de los 5 puntos originales anteriores con la cookie httpOnly secure, creación / revocación / actualización de tokens, creación de cookies de inicio de sesión, revocando cookies revocando, métodos de controlador, CSRF, etc.
Laravel Passport JWT
Para utilizar esta función, debe deshabilitar la serialización de cookies. Laravel 5.5 tiene un problema con la serialización / deserialización de los valores de las cookies. Puede leer más sobre esto aquí ( https://laravel.com/docs/5.5/upgrade )
Asegúrate de eso
tienes
<meta name="csrf-token" content="{{ csrf_token() }}">
en el encabezado de la plantilla bladeaxios está configurado para usar csrf_token en cada solicitud.
Debería tener algo como esto en resources/assets/js/bootstrap.js
window.axios.defaults.headers.common[''X-Requested-With''] = ''XMLHttpRequest'';
let token = document.head.querySelector(''meta[name="csrf-token"]'');
if (token) {
window.axios.defaults.headers.common[''X-CSRF-TOKEN''] = token.content;
} else {
console.error(''CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token'');
}
- Configure las rutas de autenticación explicadas aquí ( https://laravel.com/docs/5.5/authentication )
- El pasaporte de configuración se explica aquí ( https://laravel.com/docs/5.5/passport ).
Las partes importantes son:
- agregue el rasgo
Laravel/Passport/HasApiTokens
a su modelo deUser
- configura la opción de
driver
de la protección de autenticación deapi
para elpassport
en tuconfig/auth.php
- agregue la
/Laravel/Passport/Http/Middleware/CreateFreshApiToken::class,
middleware a su grupo de middlewareweb
enapp/Http/Kernel.php
Tenga en cuenta que probablemente puede omitir migraciones y crear clientes.
- Haga una solicitud POST para
/login
pasando sus credenciales. Usted puede hacer una solicitud AJAX o enviar el formulario normal.
Si la solicitud de inicio de sesión es AJAX (usando axios), los datos de respuesta serán html, pero lo que le interesa es el código de estado.
axios.get(
''/login,
{
email: ''[email protected]'',
password: ''secret'',
},
{
headers: {
''Accept'': ''application/json'', // set this header to get json validation errors.
},
},
).then(response => {
if (response.status === 200) {
// the cookie was set in browser
// the response.data will be HTML string but I don''t think you are interested in that
}
// do something in this case
}).catch(error => {
if (error.response.status === 422) {
// error.response.data is an object containing validation errors
}
// do something in this case
});
Al iniciar sesión, el servidor encuentra al usuario según las credenciales proporcionadas, genera un token basado en la información del usuario (id, correo electrónico ...) (este token no se guarda en ninguna parte), luego el servidor devuelve una respuesta con una cookie cifrada que contiene el token generado.
- Hacer una llamada api a una ruta protegida.
Suponiendo que tienes una ruta protegida
Route::get(''protected'', ''SomeController@protected'')->middleware(''auth:api'');
Puedes hacer una llamada ajax usando axios de manera normal. Las cookies se configuran automáticamente.
axios.get(''/api/protected'')
.then(response => {
// do something with the response
}).catch(error => {
// do something with this case of error
});
Cuando el servidor recibe la llamada, desencripta la solicitud laravel_cookie
y obtiene información del usuario (ej .: id, correo electrónico ...) Luego, con esa información del usuario, realiza una búsqueda en la base de datos para verificar si el usuario existe. Si el usuario se encuentra, el usuario está autorizado para acceder al recurso solicitado. De lo contrario se devuelve un 401.
Invalidando el token JWT. Al mencionar el comentario, no hay necesidad de preocuparse por esto, ya que este token no se guarda en ningún lugar del servidor.
Actualizar
Respecto al punto 3, Laravel 5.6 Auth tiene un nuevo método logoutOtherDevices
. Puede obtener más información aquí ( https://laracasts.com/series/whats-new-in-laravel-5-6/episodes/7 ) ya que la documentación es muy clara.
Si no puede actualizar su versión de Laravel, puede ver cómo se hace en 5.6 y construir su propia implementación para 5.5.
Punto 4 de tu pregunta. Eche un vistazo a los controladores que se encuentran en app/Http/Controllers/Auth
.
Con respecto a access_tokens y refresh_tokens, este es un enfoque totalmente diferente y más complejo. Puedes encontrar muchos tutoriales en línea que explican cómo hacerlo.
Espero eso ayude.
PD. ¡¡Ten un feliz año nuevo!! :)
También he implementado el pasaporte Laravel en mi proyecto y creo que he cubierto la mayoría de los puntos que mencionó en su pregunta.
- He utilizado la concesión de contraseña para generar un token de acceso y token de actualización. Puede seguir these pasos para configurar el pasaporte e implementar la concesión de pasaporte. En su método de inicio de sesión, debe validar las credenciales del usuario y generar los tokens y adjuntar la cookie ( Adjuntando cookie a la respuesta ) a la respuesta. Si lo necesitas te puedo dar algunos ejemplos.
- He agregado dos middleware para CORS (Manejo de los encabezados de solicitud entrantes) y para comprobar si el token de acceso entrante es válido o no, si no es válido generar el token de acceso del token de actualización almacenado (token de actualización). Te puedo mostrar el ejemplo.
- Después de iniciar sesión, todas las solicitudes del lado del cliente deben contener el encabezado de
Authorization: Bearer <token>
(Authorization: Bearer <token>
).
Déjame saber si eres claro con los puntos anteriores.
Trataré de responder esto de una manera genérica para que la respuesta sea aplicable en marcos, implementaciones e idiomas porque las respuestas a todas las preguntas pueden derivarse de las especificaciones generales del protocolo o algoritmo.
¿Qué tipo de concesión OAuth 2.0 debo usar?
Esto es lo primero que hay que decidir. Cuando se trata de SPA, las dos opciones posibles son:
- Concesión del código de autorización (recomendado, siempre que el secreto del cliente se almacene en el lado del servidor)
- Credencial de la contraseña del propietario del recurso
Las razones por las que no menciono el tipo de concesión implícita como una opción son:
- Falta el paso de autenticación del cliente al proporcionar el secreto del cliente y el código de autorización. Así que menos seguridad
- El token de acceso se envía de vuelta como un fragmento de URL (para que el token no vaya al servidor) que continuará en el historial del navegador.
- Si ocurre un ataque XSS, el script malicioso puede enviar el token al servidor remoto.
En el caso de un tipo de concesión de código de autorización, el servidor de autorización suele ser un servidor diferente del servidor de recursos. Es mejor mantener el servidor de autorización separado y utilizarlo como un servidor de autorización común para todos los SPA dentro de la organización. Esta es siempre la solución recomendada.
Aquí (en el tipo de concesión de código de autorización) el flujo se ve a continuación:
- el usuario hace clic en el botón de inicio de sesión en la página de destino del SPA
- El usuario es redirigido a la página de inicio de sesión del servidor de autorización. La identificación del cliente se proporciona en el parámetro de consulta de URL
- El usuario ingresa sus credenciales y hace clic en el botón de inicio de sesión. El nombre de usuario y la contraseña se enviarán al servidor de autorización mediante HTTP POST. Las credenciales deben enviarse en el cuerpo de la solicitud y NO en la URL (ya que las URL se registran en el historial del navegador y el servidor de aplicaciones). Además, se deben establecer los encabezados HTTP de caché adecuados, para que las credenciales no se
Cache-Control: no-cache, no-store
caché:Cache-Control: no-cache, no-store
,Pragma: no-cache
,Expires: 0
- El servidor de autorización autentica al usuario en una base de datos de usuario (por ejemplo, el servidor LDAP) donde el nombre de usuario y el hash de la contraseña del usuario (algoritmos de hashing como Argon2, PBKDF2, Bcrypt o Scrypt) se almacenan con sal aleatoria
- En una autenticación exitosa, el servidor de autorización recuperaría de su base de datos la URL de redireccionamiento contra la identificación del cliente proporcionada en el parámetro de consulta de URL. La URL de redireccionamiento es la URL del servidor de recursos
- El usuario será redirigido a un punto final del servidor de recursos con un código de autorización en el parámetro de consulta de URL
- El servidor de recursos realizará una solicitud HTTP POST al servidor de autorización para el token de acceso. El código de autorización, la identificación del cliente, el secreto del cliente debe ir en el cuerpo de la solicitud. Deben usarse cabeceras de caché apropiadas como las anteriores
- El servidor de autorización devolverá el token de acceso y el token de actualización en el cuerpo de la respuesta
- El servidor de recursos ahora redireccionará al usuario (código de respuesta HTTP 302) a la URL del SPA configurando las cookies apropiadas (que se explicarán en detalle a continuación)
Por otro lado, para el tipo de concesión de credenciales de contraseña del propietario del recurso, el servidor de autorización y el servidor de recurso son los mismos. Es más fácil de implementar y también se puede utilizar si se ajusta a los requisitos y los plazos de implementación.
También refiera a mi respuesta en esto here .
¿Por qué no debería almacenar los tokens en el almacenamiento local del navegador o en el almacenamiento de la sesión?
Muchos SPA almacenan el acceso y / o actualizan el token en el navegador localstorage o sessionstorage. La razón por la que creo que no deberíamos almacenar los tokens en estos almacenamientos del navegador es:
Si ocurre XSS, el script malicioso puede leer fácilmente los tokens desde allí y enviarlos a un servidor remoto. Ahí, el atacante o el servidor remoto no tendrían problemas para hacerse pasar por el usuario víctima.
localstorage y sessionstorage no se comparten entre subdominios. Entonces, si tenemos dos SPA que se ejecutan en subdominios diferentes, no obtendremos la funcionalidad SSO porque el token almacenado por una aplicación no estará disponible para la otra aplicación dentro de la organización
¿Por qué debo almacenar el token de acceso y / o el token de actualización en las cookies?
- Al almacenar los tokens en las cookies, podemos establecer la cookie como
secure
yhttpOnly
. Por lo tanto, si se produce XSS, el script malicioso no puede leerlo y enviarlo al servidor remoto. XSS todavía puede hacerse pasar por el usuario desde el navegador de los usuarios, pero si el navegador está cerrado, el script no puede hacer más daño.secure
indicador desecure
garantiza que los tokens no puedan enviarse a través de conexiones no seguras. SSL / TLS es obligatorio - La configuración del dominio raíz en la cookie como
domain=example.com
, por ejemplo, garantiza que la cookie sea accesible en todos los subdominios. Por lo tanto, diferentes aplicaciones y servidores dentro de la organización pueden usar los mismos tokens. Se requiere iniciar sesión solo una vez
¿Cómo validar el token?
Los tokens son usualmente tokens de JWT. Normalmente los contenidos del token no son secretos. Por lo tanto, por lo general no están encriptados. Si se requiere cifrado (tal vez porque también se está pasando información confidencial dentro del token), hay una especificación JWE separada. Incluso si no se requiere el cifrado, debemos garantizar la integridad de los tokens. Nadie (el usuario o el atacante) debe poder modificar los tokens. Si lo hacen, el servidor debería poder detectar eso y rechazar todas las solicitudes con los tokens falsificados. Para garantizar esta integridad, los tokens JWT se firman digitalmente utilizando un algoritmo como HmacSHA256. Para generar esta firma, se requiere una clave secreta. El servidor de autorización será el propietario y protegerá el secreto. Cuando se invoca la api del servidor de autorización para validar un token, el servidor de autorización volverá a calcular el HMAC en el token pasado. Si no coincide con la entrada HMAC, devuelve una respuesta negativa. El token JWT se devuelve o almacena en un formato codificado en Base64.
Sin embargo, para cada llamada a la API en el servidor de recursos, el servidor de autorización no participa para validar el token. El servidor de recursos puede almacenar en caché los tokens emitidos por el servidor de autorización. El servidor de recursos puede usar una cuadrícula de datos en memoria (es decir, Redis) o, si no puede almacenarse todo en la RAM, un DB basado en LSM (a saber, Riak con Level DB) para almacenar los tokens.
Para cada llamada a la API, el servidor de recursos verificará su caché.
- Si el token en la cookie de solicitud no se encuentra en el caché, el usuario será redirigido a la página de inicio de sesión al restablecer las cookies.
- Si el token se encuentra en el caché, pero está vencido (nota, los tokens JWT generalmente contienen el nombre de usuario y la fecha de caducidad entre otras cosas), el servidor de recursos solicitará al servidor de autorización que proporcione un token de acceso nuevo. En el cuerpo de la solicitud, el servidor de recursos proporcionará el ID del cliente, el secreto del cliente y el token de actualización (el token de actualización, que también es un token JWT, puede permanecer incrustado en el token de acceso mismo, o se puede configurar en una cookie separada)
- El servidor de autorización validaría entonces el token de actualización. Si se encuentra que el token de actualización es válido, se devolverá un nuevo token de acceso. De esta forma el usuario se guarda al ingresar su nombre de usuario y contraseña
- Si el token de actualización ha caducado, el servidor de autorización devolverá una respuesta negativa y el servidor de recursos redirigirá al usuario a la página de inicio de sesión
¿Por qué necesitamos dos tokens: token de acceso y token de actualización?
El token de acceso suele tener un período de validez corto, es decir, 30 minutos. El token de actualización usualmente tiene un período de validez más largo, por ejemplo, 6 meses. Si el token de acceso está comprometido de alguna manera, el atacante puede hacerse pasar por el usuario víctima solo mientras el token de acceso sea válido. Dado que el atacante no tendrá el secreto del cliente, no puede solicitar al servidor de autorización un nuevo token de acceso.
Si este breve período de validez del token de acceso ayuda al servidor de autorización a revocar los tokens emitidos de los clientes, si es necesario. El servidor de autorización también puede mantener un caché de los tokens emitidos. Los administradores del sistema pueden, si es necesario, marcar los tokens de ciertos usuarios como revocados. Al expirar el token de acceso, cuando el servidor de recursos vaya al servidor de autorización, se forzará al usuario a iniciar sesión nuevamente.
¿Qué pasa con CSRF?
Para proteger al usuario de CSRF, podemos seguir el enfoque seguido en marcos como Angular (como se explica en la documentation HttpClient de Angular documentation donde el servidor tiene que enviar una cookie no HttpOnly (en otras palabras, una cookie legible) que contiene una única impredecible valor para esa sesión en particular. Debe ser un valor aleatorio criptográficamente sólido. El cliente siempre leerá la cookie y enviará el valor en un encabezado HTTP personalizado (excepto las solicitudes GET y HEAD que no deben tener ninguna lógica de cambio de estado. Nota CSRF no puede leer nada de la aplicación web de destino debido a la misma política de origen, por lo que el servidor puede verificar el valor del encabezado y la cookie. Dado que los formularios de dominio cruzado no pueden leer la cookie o establecer un encabezado personalizado, en caso de solicitudes CSRF , faltará el valor del encabezado personalizado y el servidor podrá detectar el ataque
Para proteger la aplicación del inicio de sesión CSRF, siempre verifique el encabezado del remitente y acepte las solicitudes solo cuando el
referer
sea un dominio de confianza. Si el encabezado del remitente está ausente o es un dominio no incluido en la lista blanca, simplemente rechace la solicitud. Cuando se usa SSL / TLS, elreferrer
suele estar presente. Las páginas de destino (que son en su mayoría informativas y que no contienen el formulario de inicio de sesión o cualquier contenido seguro pueden estar un poco relajadas y permitir solicitudes con el encabezado faltante del remitente)TRACE
método HTTPTRACE
debe estar bloqueado en el servidor, ya que puede usarse para leer la cookiehttpOnly
Además, establezca el encabezado
Strict-Transport-Security: max-age=<expire-time>; includeSubDomains
Strict-Transport-Security: max-age=<expire-time>; includeSubDomains
para permitir solo conexiones seguras para evitar que cualquier persona en el medio sobrescriba las cookies CSRF de un subdominio
Finalmente, SSL / TLS es obligatorio para todas las comunicaciones; a partir de hoy, las versiones de TLS por debajo de 1.1 no son aceptables para el cumplimiento de PCI / DSS. Se deben utilizar las suites de cifrado adecuadas para garantizar el secreto a futuro y el cifrado autenticado
- Laravel Passport es una implementación del servidor OAuth de The PHP League.
- El tipo de concesión de contraseña se puede utilizar para la autenticación de nombre de usuario + contraseña
- Recuerde ocultar sus credenciales de cliente haciendo la solicitud de autenticación en un proxy
- Guarde el token de actualización en una cookie HttpOnly para minimizar el riesgo de ataques XSS
Más información se puede ver aquí.
http://esbenp.github.io/2017/03/19/modern-rest-api-laravel-part-4/