¿Cómo implementar Authorization CodeFlow con Proof Key for Code Exchange usando la API de Twitter?
Hoy vamos a aprender a implementar el flujo de autorización conocido como Authorization CodeFlow con Proof Key for Code Exchange (PKCE) en la API de Twitter. Este método es ideal para desarrollar aplicaciones en las que el secreto del cliente no se puede almacenar de forma segura. A continuación, te enseñaré paso a paso cómo lograrlo utilizando la API de Twitter.
¿Cómo crear una aplicación en la plataforma de desarrolladores de Twitter?
El primer paso es ir al Twitter Developer Portal para crear tu aplicación. Sigue estas indicaciones:
Crea un proyecto y, dentro de él, puedes crear hasta tres aplicaciones.
Ve a la sección Overview y selecciona la opción para agregar una nueva app.
Elige el entorno, en este ejemplo usaremos "staging".
Introduce un nombre significativo para tu aplicación.
Establece la autenticación requerida:
Permisos de solo lectura.
Una app nativa (cliente público).
Proporciona un URL de callback válido (en modo desarrollo, podrías usar http://localhost:3003/api/callback).
Guarda la configuración.
Copia el Client ID y el Client Secret para usarlos más adelante, asegurándote de almacenarlos de manera segura.
¿Cómo implementar la solicitud de autorización?
Una vez tengamos configurada nuestra app, es momento de establecer la solicitud de autorización con PKCE. Aquí los pasos esenciales:
// Genera valores para state y codeVerifierconst state = randomString.generate(16);const codeVerifier = randomString.generate(128);const codeChallenge =generateCodeChallenge(codeVerifier);// Función que hashea el codeVerifier// Configura los parámetros para el authRequestconst authRequest ={response_type:'code',client_id:CLIENT_ID,redirect_uri:'http://localhost:3003/api/callback',scope:'read:user, read:tweets',state: state,code_challenge: codeChallenge,code_challenge_method:'S256',};// Almacena state y codeVerifier en cookiesdocument.cookie=`state=${state}`;document.cookie=`codeVerifier=${codeVerifier}`;
Al completar este setup, el cliente envía la solicitud de autorización al servidor de Twitter, quien verifica la validez de la llamada.
¿Cómo verificar y obtener el token de acceso?
Cuando el usuario autoriza la app, Twitter redirige a nuestra URL de callback, donde debemos:
Verificar que el estado recibido en la query coincide con el estado almacenado:
Si no coincide, se muestra un error.
Intercambiar el código de autorización por un token de acceso enviando el codeVerifier al servidor de autorización.
// Verificación del estadoif(request.query.state!== cookies.state){thrownewError('State does not match');}// Intercambio de código por tokenasyncfunctionexchangeCodeForToken(authCode){const data ={grant_type:'authorization_code',client_id:CLIENT_ID,redirect_uri:'http://localhost:3003/api/callback',code: authCode,code_verifier: cookies.codeVerifier,};const response =awaitfetch(TWITTER_TOKEN_ENDPOINT,{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:newURLSearchParams(data),});return response.json();}
Con el token de acceso, podrás interactuar con la API de Twitter accediendo a los recursos permitidos según los permisos definidos.
¿Cómo manejar restricciones de CORS en Twitter?
Twitter aplica políticas CORS que pueden restringir las solicitudes directas desde el cliente. Para solucionar esto, puedes crear un proxy en el servidor:
// Implementación de middleware en el backend para evitar problemas de CORSapp.post('/api/cors',async(req, res)=>{const url = req.body.url;const response =awaitfetch(url,{headers:{Authorization:`Bearer ${ACCESS_TOKEN}`},});const data =await response.json(); res.send(data);});
Finalmente, desde el cliente, puedes llamar al middleware en lugar de hacerlo directamente a la API de Twitter.
¡Te animo a que pongas en práctica esta implementación! Intenta usar otro servicio que no sea Twitter para reforzar tus conocimientos. En la próxima sesión, exploraremos cómo implementar Implicit Flow con Twitch. ¡Sigue adelante, estás a un paso más cerca de dominar OAuth 2.0!
Me agrego a la propuesta jeje !! curso de criptografia por favor !
Porque no habra mostrado la implementación de este flujo usando una SPA
Por qué enviamos el client id y secret si el flujo en teoría solo acepta el code verifier y el authorization code a la hora de solicitar el access token al endpoint /token?
¡Muy buena pregunta! la nueva versión de la API de Twitter tiene un bug con las apps "no seguras" (a la hora de grabar el curso) que corren del lado del cliente.
Por eso usamos la versión de app segura que pide además del client secret.
Sí bien OAuth es un estándar, no siempre las compañías lo implementan rigurosamente o pueden haber leves variaciones que no necesariamente contradicen la especificación.
Estoy algo confundido con la diferencia entre Auth Code Flow y Auth Code Flow w/ PKCE, comparando este ejemplo con el anterior.
Pensé que un cliente no seguro era una SPA o similar sin un servidor/backend y es allí donde es necesario usar PKCE, pero aquí estamos usando el "backend" de la aplicación de next.js al igual que en el ejemplo anterior, y parecen el mismo escenario desde mi perspectiva.
Alguien me podría aclarar mas porque usamos un servidor para obtener el token de autorización en vez del frontend para este flujo? es necesario? @glrodasz
PKCE no tiene restricción a un cliente seguro o inseguro.
En Twitter de hecho se puede configurar cualquiera de los dos canales, uno sería enviando el Client Secret y el otro no.
¿Que seria redundante? Sí, pero si se puede usar PKCE siempre mejor.
tiene sentido, gracias por aclarar, profe!
Es posible usar una callback URI como localhost durante el desarrollo de aplicaciones porque permite a los desarrolladores probar flujos de autenticación sin necesidad de un servidor en producción. Cuando se establece la callback URI en localhost, el servidor de autorización puede redirigir a la aplicación en la máquina local del desarrollador después de que el usuario otorgue acceso. Esto es útil para pruebas y desarrollo, ya que evita la complejidad de manejar URLs en un entorno de producción. En un entorno de producción, se deben usar URLs válidas y accesibles públicamente.
Hola, tengo una duda. Si usamos cookies para guardar el token (una cookie segura), es necesario implementar el método del "state" para evitar ataques CSRF ?
Los ataques a un almacenamiento no seguro como el local o session storage son diferentes al CSRF, por ende es recomendado usar el state de todas formas.
Es mejor guardar el token y el code_verifier en una cookie que en local storage?
Sí, el problema es que el local storage no es seguro, siempre una cookie segura del lado del servidor va a ser mejor opción.
Un caso es el vector de ataque de las dependencias, si instalas, supongamos "una versión corrupta de lodash" este código de tercero puede acceder a tu local storage sin problema, por ende al tus tokens.
Entonces cualquier inyección de JavaScript puede terminar en un acceso a nuestros tokens.
{"title":"Unsupported Authentication","detail":"Authenticating with OAuth 2.0 Application-Only is forbidden for this endpoint. Supported authentication types are [OAuth 1.0a User Context, OAuth 2.0 User Context].","type":"https://api.twitter.com/2/problems/unsupported-authentication","status":403}
Por si a alguien le llega a pasar lo mismo, borré las cookies y volví a probar, ya todo funciona. Al parecer cometí un error al inicio de la prueba al momento de guardar el access_token en una cookie, y al final se estaba pegando Path=/.
A pesar de haber corregido el error, se quedó la cookie mala.
Puedo estar equivocado, pero creo que es necesario agregar un return en el if que valida que el state sea el mismo que fue enviado, ya que res.end() responde al cliente, pero es posible que no termine el flujo de la función.
exportdefaultasyncfunctionhandler(req, res){const cookies = cookie.parse(req.headers.cookie);if(req.query.state!== cookies.state){ res.writeHead(302,{Location:"/#?error=ERROR_STATE_MISMATCH"}); res.end();return;// Detener la ejecución aquí}const options ={method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",},body: querystring.stringify({code: req.query.code,redirect_uri:REDIRECT_URI,grant_type:"authorization_code",client_id:CLIENT_ID,code_verifier: cookies.verifier,}),};try{const response =awaitfetch(TWITTER_TOKEN_URL, options);const data =await response.json(); res.setHeader("Set-Cookie",`access_token=${data.access_token}; Path=/; HttpOnly`); res.writeHead(302,{Location:"/home"}); res.end();}catch(error){console.error(error); res.status(500).send("Internal Server Error");// Agregar manejo de errores adecuado}}
Incluso aunque este método sea recomendado para SPAs, se necesitara usar un canal seguro como el backend para pedir el token. Entonces... sería mejor usar el Auth Code Flow normal, no?
Ya que de cualquier manera, para utilizar este flujo, vamos a necesitar un backend
¡Hola Irving! Estoy aprendiendo también, pero lo que entiendo acá es que no es necesario un canal seguro como el back-end.
¿Por qué? Porque no es necesario para este flujo almacenar y utilizar el CLIENT_SECRET, en la clase Guille usa Next con API Routes que permite usar un back-end, pero ya viene usándolo de la clase anterior para el flujo Auth Code Flow que sí requiere un back-end.
Creo que por practicidad y que veamos la misma base de código de ejemplo para cada flujo y no confundirnos.
En este flujo en lugar de enviar el CLIENTE_SECRET, que no lo tenemos, ya que "no estamos en un canal seguro" (imaginemos que no, ya que el flujo es para esos casos), entonces enviamos el code_verifier para pedir el access_token.
Y el Authorization Server valida este code_verifier con el code_challenge y code_challenge_method enviados previamente en el authorization request.
Si bien Guille copia el CLIENT_SECRET a principio y lo coloca en el archivo de variables de entorno, realmente lo único necesario fue el CLIENT_ID en ese proceso. Es lo que entendí.