Customize Consent Preferences

We use cookies to help you navigate efficiently and perform certain functions. You will find detailed information about all cookies under each consent category below.

The cookies that are categorized as "Necessary" are stored on your browser as they are essential for enabling the basic functionalities of the site. ... 

Always Active

Necessary cookies are required to enable the basic features of this site, such as providing secure log-in or adjusting your consent preferences. These cookies do not store any personally identifiable data.

No cookies to display.

Functional cookies help perform certain functionalities like sharing the content of the website on social media platforms, collecting feedback, and other third-party features.

No cookies to display.

Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics such as the number of visitors, bounce rate, traffic source, etc.

No cookies to display.

Performance cookies are used to understand and analyze the key performance indexes of the website which helps in delivering a better user experience for the visitors.

No cookies to display.

Advertisement cookies are used to provide visitors with customized advertisements based on the pages you visited previously and to analyze the effectiveness of the ad campaigns.

No cookies to display.

Implementar login con LinkedIn en React y OpenID Connect

Tiempo de lectura: 6 minutos

Hoy vamos a ver cómo implementar login mediante LinkedIn mediante OpenID connect usando React.

Campo de arroz - Pexels

Lo primero que tienes que hacer es registrar tu aplicación en Linkedin para obtener el client ID y el private ID:

Una vez creado, vamos a crear un componente llamado LinkedInOauth.tsx

Uso TypeScript:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { LinkedinClientId, LinkedinClientIdSecret } from '@/util/Codes';
import React from 'react';
const CLIENT_ID = "client_id_linkedin";
const CLIENT_SECRET = "client_id_secret_linkedin;
const REDIRECT_URL = encodeURIComponent('http://localhost:3000/login');
const SCOPES = 'profile%20email%20openid';
const STATE = "random_value";
const linkedinOAuthURL = `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URL}&scope=${SCOPES}`;
const LinkedInOAuth = () => {
const handleLogin = async (code) => {
// Exchange the code for an access token
const data = await fetch('https://www.linkedin.com/oauth/v2/accessToken', {
method: 'POST',
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: REDIRECT_URL,
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET
})
}).then((response) => response.json());
const accessToken = data.access_token;
// Fetch the user's LinkedIn profile
const userProfile: any = await fetch(
'https://api.linkedin.com/v2/me?projection=(id,firstName,lastName)',
{
headers: {
Authorization: `Bearer ${accessToken}`
}
}
);
// Handle the user profile data (e.g., store it in your database and log the user in)
console.log(
`Welcome, ${userProfile.data.firstName.localized.en_US} ${userProfile.data.lastName.localized.en_US}!`
);
};
const handleLinkedInCallback = () => {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const code = urlParams.get('code');
if (code) handleLogin(code);
};
React.useEffect(() => {
handleLinkedInCallback();
}, []);
return (
<div>
<a href={linkedinOAuthURL}>Sign in with LinkedIn</a>
</div>
);
};
export default LinkedInOAuth;
import { LinkedinClientId, LinkedinClientIdSecret } from '@/util/Codes'; import React from 'react'; const CLIENT_ID = "client_id_linkedin"; const CLIENT_SECRET = "client_id_secret_linkedin; const REDIRECT_URL = encodeURIComponent('http://localhost:3000/login'); const SCOPES = 'profile%20email%20openid'; const STATE = "random_value"; const linkedinOAuthURL = `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URL}&scope=${SCOPES}`; const LinkedInOAuth = () => { const handleLogin = async (code) => { // Exchange the code for an access token const data = await fetch('https://www.linkedin.com/oauth/v2/accessToken', { method: 'POST', body: new URLSearchParams({ grant_type: 'authorization_code', code, redirect_uri: REDIRECT_URL, client_id: CLIENT_ID, client_secret: CLIENT_SECRET }) }).then((response) => response.json()); const accessToken = data.access_token; // Fetch the user's LinkedIn profile const userProfile: any = await fetch( 'https://api.linkedin.com/v2/me?projection=(id,firstName,lastName)', { headers: { Authorization: `Bearer ${accessToken}` } } ); // Handle the user profile data (e.g., store it in your database and log the user in) console.log( `Welcome, ${userProfile.data.firstName.localized.en_US} ${userProfile.data.lastName.localized.en_US}!` ); }; const handleLinkedInCallback = () => { const queryString = window.location.search; const urlParams = new URLSearchParams(queryString); const code = urlParams.get('code'); if (code) handleLogin(code); }; React.useEffect(() => { handleLinkedInCallback(); }, []); return ( <div> <a href={linkedinOAuthURL}>Sign in with LinkedIn</a> </div> ); }; export default LinkedInOAuth;
import { LinkedinClientId, LinkedinClientIdSecret } from '@/util/Codes';
import React from 'react';

const CLIENT_ID = "client_id_linkedin";
const CLIENT_SECRET = "client_id_secret_linkedin;
const REDIRECT_URL = encodeURIComponent('http://localhost:3000/login');
const SCOPES = 'profile%20email%20openid';
const STATE = "random_value";
const linkedinOAuthURL = `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URL}&scope=${SCOPES}`;

const LinkedInOAuth = () => {
    const handleLogin = async (code) => {
        // Exchange the code for an access token
        const data = await fetch('https://www.linkedin.com/oauth/v2/accessToken', {
            method: 'POST',
            body: new URLSearchParams({
                grant_type: 'authorization_code',
                code,
                redirect_uri: REDIRECT_URL,
                client_id: CLIENT_ID,
                client_secret: CLIENT_SECRET
            })
        }).then((response) => response.json());

        const accessToken = data.access_token;

        // Fetch the user's LinkedIn profile
        const userProfile: any = await fetch(
            'https://api.linkedin.com/v2/me?projection=(id,firstName,lastName)',
            {
                headers: {
                    Authorization: `Bearer ${accessToken}`
                }
            }
        );

        // Handle the user profile data (e.g., store it in your database and log the user in)
        console.log(
            `Welcome, ${userProfile.data.firstName.localized.en_US} ${userProfile.data.lastName.localized.en_US}!`
        );
    };

    const handleLinkedInCallback = () => {
        const queryString = window.location.search;
        const urlParams = new URLSearchParams(queryString);
        const code = urlParams.get('code');
        if (code) handleLogin(code);
    };

    React.useEffect(() => {
        handleLinkedInCallback();
    }, []);

    return (
        <div>
            <a href={linkedinOAuthURL}>Sign in with LinkedIn</a>
        </div>
    );
};

export default LinkedInOAuth;

Debes completar la parte de const CLIENT_ID = «CLIENT_ID»; añadiendo el código de cliente LinkedIn.
Tambien podemos añadir const CLIENT_SECRET para poder verificar el token (aunque recomiendo verificarlo en el servidor). añadiendo el código de cliente secret LinkedIn

La variable const state = ‘random_string_for_csrf_protection’; se puede generar de forma aleatoria. Si queremos utilizarla para evitar ataques de CSFR (Cross-Site Request Forgery) lo explicaré a continuación.

En const redirectUri indicaremos cual es nuestra URL de redirección permitida en LinkedIn.

El código de handleLogin tendremos que completarlo con nuestro back para poder verificar el token linkedin obtenido. Aunque incluyo uno para hacerlo en cliente, no es nada recomendable.

En este código incluyo la verificación de Cross-Site:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import React, { useEffect } from 'react';
const CLIENT_ID = "client_id_linkedin";
const CLIENT_SECRET = "client_id_secret_linkedin;
const REDIRECT_URL = 'http://localhost:3000/login';
const SCOPES = 'profile%20email%20openid';
// Función para generar un string aleatorio (para el estado de CSRF)
const generateRandomState = (length = 16) => {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
return Array.from({ length }, () => characters.charAt(Math.floor(Math.random() * characters.length))).join('');
};
const LinkedInOAuth = () => {
// Generar la URL de autenticación de LinkedIn con el estado CSRF
const createLinkedInAuthURL = () => {
const state = generateRandomState();
localStorage.setItem('linkedin_oauth_state', state);
return `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${CLIENT_ID}&redirect_uri=${encodeURIComponent(
REDIRECT_URL
)}&scope=${SCOPES}&state=${state}`;
};
const handleLogin = async (code) => {
// Intercambiar el código de autorización por un token de acceso
const tokenResponse = await fetch('https://www.linkedin.com/oauth/v2/accessToken', {
method: 'POST',
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: REDIRECT_URL,
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
}),
}).then((response) => response.json());
const accessToken = tokenResponse.access_token;
// Obtener el perfil del usuario de LinkedIn
const userProfile = await fetch(
'https://api.linkedin.com/v2/me?projection=(id,firstName,lastName)',
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
}
).then((res) => res.json());
// Procesar los datos del perfil del usuario
console.log(
`Welcome, ${userProfile.firstName.localized.en_US} ${userProfile.lastName.localized.en_US}!`
);
};
const handleLinkedInCallback = () => {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const code = urlParams.get('code');
const stateFromURL = urlParams.get('state');
const storedState = localStorage.getItem('linkedin_oauth_state');
// Verificar si el estado en la URL coincide con el almacenado (para evitar ataques CSRF)
if (stateFromURL !== storedState) {
console.error('CSRF protection failed: State mismatch.');
return;
}
if (code) {
handleLogin(code);
}
};
useEffect(() => {
handleLinkedInCallback();
}, []);
return (
<div>
<a href={createLinkedInAuthURL()}>Sign in with LinkedIn</a>
</div>
);
};
export default LinkedInOAuth;
import React, { useEffect } from 'react'; const CLIENT_ID = "client_id_linkedin"; const CLIENT_SECRET = "client_id_secret_linkedin; const REDIRECT_URL = 'http://localhost:3000/login'; const SCOPES = 'profile%20email%20openid'; // Función para generar un string aleatorio (para el estado de CSRF) const generateRandomState = (length = 16) => { const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; return Array.from({ length }, () => characters.charAt(Math.floor(Math.random() * characters.length))).join(''); }; const LinkedInOAuth = () => { // Generar la URL de autenticación de LinkedIn con el estado CSRF const createLinkedInAuthURL = () => { const state = generateRandomState(); localStorage.setItem('linkedin_oauth_state', state); return `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${CLIENT_ID}&redirect_uri=${encodeURIComponent( REDIRECT_URL )}&scope=${SCOPES}&state=${state}`; }; const handleLogin = async (code) => { // Intercambiar el código de autorización por un token de acceso const tokenResponse = await fetch('https://www.linkedin.com/oauth/v2/accessToken', { method: 'POST', body: new URLSearchParams({ grant_type: 'authorization_code', code, redirect_uri: REDIRECT_URL, client_id: CLIENT_ID, client_secret: CLIENT_SECRET, }), }).then((response) => response.json()); const accessToken = tokenResponse.access_token; // Obtener el perfil del usuario de LinkedIn const userProfile = await fetch( 'https://api.linkedin.com/v2/me?projection=(id,firstName,lastName)', { headers: { Authorization: `Bearer ${accessToken}`, }, } ).then((res) => res.json()); // Procesar los datos del perfil del usuario console.log( `Welcome, ${userProfile.firstName.localized.en_US} ${userProfile.lastName.localized.en_US}!` ); }; const handleLinkedInCallback = () => { const queryString = window.location.search; const urlParams = new URLSearchParams(queryString); const code = urlParams.get('code'); const stateFromURL = urlParams.get('state'); const storedState = localStorage.getItem('linkedin_oauth_state'); // Verificar si el estado en la URL coincide con el almacenado (para evitar ataques CSRF) if (stateFromURL !== storedState) { console.error('CSRF protection failed: State mismatch.'); return; } if (code) { handleLogin(code); } }; useEffect(() => { handleLinkedInCallback(); }, []); return ( <div> <a href={createLinkedInAuthURL()}>Sign in with LinkedIn</a> </div> ); }; export default LinkedInOAuth;
import React, { useEffect } from 'react';

const CLIENT_ID = "client_id_linkedin";
const CLIENT_SECRET = "client_id_secret_linkedin;
const REDIRECT_URL = 'http://localhost:3000/login';
const SCOPES = 'profile%20email%20openid';

// Función para generar un string aleatorio (para el estado de CSRF)
const generateRandomState = (length = 16) => {
  const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  return Array.from({ length }, () => characters.charAt(Math.floor(Math.random() * characters.length))).join('');
};

const LinkedInOAuth = () => {
  // Generar la URL de autenticación de LinkedIn con el estado CSRF
  const createLinkedInAuthURL = () => {
    const state = generateRandomState();
    localStorage.setItem('linkedin_oauth_state', state);

    return `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${CLIENT_ID}&redirect_uri=${encodeURIComponent(
      REDIRECT_URL
    )}&scope=${SCOPES}&state=${state}`;
  };

  const handleLogin = async (code) => {
    // Intercambiar el código de autorización por un token de acceso
    const tokenResponse = await fetch('https://www.linkedin.com/oauth/v2/accessToken', {
      method: 'POST',
      body: new URLSearchParams({
        grant_type: 'authorization_code',
        code,
        redirect_uri: REDIRECT_URL,
        client_id: CLIENT_ID,
        client_secret: CLIENT_SECRET,
      }),
    }).then((response) => response.json());

    const accessToken = tokenResponse.access_token;

    // Obtener el perfil del usuario de LinkedIn
    const userProfile = await fetch(
      'https://api.linkedin.com/v2/me?projection=(id,firstName,lastName)',
      {
        headers: {
          Authorization: `Bearer ${accessToken}`,
        },
      }
    ).then((res) => res.json());

    // Procesar los datos del perfil del usuario
    console.log(
      `Welcome, ${userProfile.firstName.localized.en_US} ${userProfile.lastName.localized.en_US}!`
    );
  };

  const handleLinkedInCallback = () => {
    const queryString = window.location.search;
    const urlParams = new URLSearchParams(queryString);
    const code = urlParams.get('code');
    const stateFromURL = urlParams.get('state');
    const storedState = localStorage.getItem('linkedin_oauth_state');

    // Verificar si el estado en la URL coincide con el almacenado (para evitar ataques CSRF)
    if (stateFromURL !== storedState) {
      console.error('CSRF protection failed: State mismatch.');
      return;
    }

    if (code) {
      handleLogin(code);
    }
  };

  useEffect(() => {
    handleLinkedInCallback();
  }, []);

  return (
    <div>
      <a href={createLinkedInAuthURL()}>Sign in with LinkedIn</a>
    </div>
  );
};

export default LinkedInOAuth;

Explicación del código:

Redirigir al usuario a LinkedIn:

  • La función linkedInAuthUrl construye la URL de autorización con los parámetros necesarios (client_id, redirect_uri, scope, etc.).
  • La función handleLogin simplemente redirige al usuario a esa URL de LinkedIn.

Capturar el código de autorización:

  • Usamos useEffect para detectar si en la URL hay un código de autorización (code) cuando LinkedIn redirige de vuelta a tu aplicación.
  • Una vez que tienes el code, llamas a la función fetchAccessToken para enviarlo al backend.

Intercambiar el código por un token de acceso:

  • En fetchAccessToken, haces una solicitud al backend para obtener el token de acceso a través del endpoint /auth/linkedin/token, que maneja la lógica del intercambio de código en el backend.
  • Si el intercambio es exitoso, se almacena el token de acceso en el estado (setAccessToken).

Y para utilizarlo haremos:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<LinkedInOauth/>
<LinkedInOauth/>
<LinkedInOauth/>

Distintos Scopes disponibles en Linkedin:

LinkedIn ofrece varios scopes que determinan los tipos de datos y permisos que tu aplicación puede solicitar del usuario. A continuación te dejo una lista de los scopes más comunes que puedes usar con LinkedIn OAuth 2.0:

r_liteprofile (Perfil básico)

  • Acceso al perfil básico del usuario.
  • Este es el perfil reducido que contiene campos como el nombre, apellido, la foto de perfil, y el ID de LinkedIn.
  • Datos disponibles:
    • First name
    • Last name
    • Profile picture (URL)
    • ID de LinkedIn
    Ejemplo de uso:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
scope=r_liteprofile
scope=r_liteprofile
   scope=r_liteprofile

r_emailaddress (Correo electrónico)

  • Permite obtener la dirección de correo electrónico principal asociada con la cuenta de LinkedIn del usuario.
  • Datos disponibles:
    • Dirección de correo electrónico del usuario.
    Ejemplo de uso:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
scope=r_emailaddress
scope=r_emailaddress
   scope=r_emailaddress

w_member_social (Publicar actualizaciones en nombre del usuario)

  • Permite a tu aplicación publicar contenido (artículos, actualizaciones) en el perfil del usuario.
  • Con este permiso, tu aplicación puede compartir contenido en la cuenta de LinkedIn del usuario de manera programática. Ejemplo de uso:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
scope=w_member_social
scope=w_member_social
   scope=w_member_social

Scopes Adicionales:

r_fullprofile (Perfil completo) (Deprecado y muy limitado)

  • Este permiso permitía obtener datos más detallados del perfil del usuario, pero ya no está disponible para la mayoría de las aplicaciones.
  • Ahora solo algunas aplicaciones verificadas por LinkedIn pueden solicitar acceso a perfiles completos.

rw_organization_admin (Administración de organizaciones)

  • Permite gestionar las páginas de empresas y organizaciones en LinkedIn a las que el usuario tiene permisos administrativos.
  • Datos disponibles:
    • Publicar contenido en nombre de una página de empresa.
    • Ver los miembros de una organización.
    Ejemplo de uso:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
scope=rw_organization_admin
scope=rw_organization_admin
   scope=rw_organization_admin

r_organization_social (Acceso a datos de organización)

  • Permite acceder a la información y contenido social de una organización.
  • Datos disponibles:
    • Obtener el contenido publicado por la organización en LinkedIn.
    Ejemplo de uso:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
scope=r_organization_social
scope=r_organization_social
   scope=r_organization_social

Usos Comunes de Scopes:

Para un login básico con LinkedIn, se suelen utilizar:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
scope=r_liteprofile r_emailaddress
scope=r_liteprofile r_emailaddress
scope=r_liteprofile r_emailaddress

Si tu aplicación también necesita publicar actualizaciones o contenido en LinkedIn en nombre del usuario, puedes añadir:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
scope=r_liteprofile r_emailaddress w_member_social
scope=r_liteprofile r_emailaddress w_member_social
scope=r_liteprofile r_emailaddress w_member_social

Notas Importantes:

  • Privacidad y revisión de LinkedIn: Si solicitas permisos avanzados (como publicar en nombre de un usuario o acceder a datos de una organización), tu aplicación podría necesitar pasar por un proceso de revisión y aprobación por parte de LinkedIn.
  • Consentimiento del usuario: El usuario siempre debe aprobar los permisos que solicitas en el proceso de autenticación.

Extra: Protección contra ataques CSRF.

Para generar un valor aleatorio para state y protegerte contra ataques CSRF (Cross-Site Request Forgery), puedes crear una cadena aleatoria utilizando funciones de JavaScript. Esto se hace comúnmente en el frontend y se almacena temporalmente, por ejemplo, en el localStorage o en una cookie para verificarlo más adelante cuando LinkedIn redirija de vuelta a tu aplicación.

Generar cadena aleatoria:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const generateRandomString = (length) => {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
};
// Generar el 'state' dinámicamente
const state = generateRandomString(16); // 16 es el tamaño recomendado
const generateRandomString = (length) => { const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let result = ''; const charactersLength = characters.length; for (let i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); } return result; }; // Generar el 'state' dinámicamente const state = generateRandomString(16); // 16 es el tamaño recomendado
const generateRandomString = (length) => {
  const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  let result = '';
  const charactersLength = characters.length;
  for (let i = 0; i < length; i++) {
    result += characters.charAt(Math.floor(Math.random() * charactersLength));
  }
  return result;
};

// Generar el 'state' dinámicamente
const state = generateRandomString(16); // 16 es el tamaño recomendado

Generar y almacenar el state antes de redirigir a LinkedIn:

  • Puedes almacenar el valor de state en localStorage para que luego puedas verificarlo cuando LinkedIn redirija de vuelta a tu app.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const handleLogin = () => {
const state = generateRandomString(16);
localStorage.setItem('linkedin_oauth_state', state); // Almacenar el state en localStorage
const clientId = 'TU_CLIENT_ID';
const redirectUri = encodeURIComponent('http://localhost:3000/auth/linkedin/callback');
const scope = 'r_liteprofile r_emailaddress';
window.location.href = `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${clientId}&redirect_uri=${redirectUri}&state=${state}&scope=${scope}`;
};
const handleLogin = () => { const state = generateRandomString(16); localStorage.setItem('linkedin_oauth_state', state); // Almacenar el state en localStorage const clientId = 'TU_CLIENT_ID'; const redirectUri = encodeURIComponent('http://localhost:3000/auth/linkedin/callback'); const scope = 'r_liteprofile r_emailaddress'; window.location.href = `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${clientId}&redirect_uri=${redirectUri}&state=${state}&scope=${scope}`; };
const handleLogin = () => {
  const state = generateRandomString(16);
  localStorage.setItem('linkedin_oauth_state', state); // Almacenar el state en localStorage

  const clientId = 'TU_CLIENT_ID';
  const redirectUri = encodeURIComponent('http://localhost:3000/auth/linkedin/callback');
  const scope = 'r_liteprofile r_emailaddress';

  window.location.href = `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${clientId}&redirect_uri=${redirectUri}&state=${state}&scope=${scope}`;
};
  1. Verificar el state al recibir la redirección:
  • Cuando LinkedIn redirija de vuelta a tu aplicación con el state como parámetro, debes compararlo con el valor almacenado en localStorage para asegurarte de que coincidan.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
useEffect(() => {
const searchParams = new URLSearchParams(location.search);
const code = searchParams.get('code');
const returnedState = searchParams.get('state');
const storedState = localStorage.getItem('linkedin_oauth_state'); // Recuperar el state almacenado
if (returnedState !== storedState) {
setError('CSRF protection failed: State does not match');
return;
}
if (code) {
// Proceder con el intercambio de código por token
fetchAccessToken(code);
}
}, [location]);
useEffect(() => { const searchParams = new URLSearchParams(location.search); const code = searchParams.get('code'); const returnedState = searchParams.get('state'); const storedState = localStorage.getItem('linkedin_oauth_state'); // Recuperar el state almacenado if (returnedState !== storedState) { setError('CSRF protection failed: State does not match'); return; } if (code) { // Proceder con el intercambio de código por token fetchAccessToken(code); } }, [location]);
useEffect(() => {
  const searchParams = new URLSearchParams(location.search);
  const code = searchParams.get('code');
  const returnedState = searchParams.get('state');
  const storedState = localStorage.getItem('linkedin_oauth_state'); // Recuperar el state almacenado

  if (returnedState !== storedState) {
    setError('CSRF protection failed: State does not match');
    return;
  }

  if (code) {
    // Proceder con el intercambio de código por token
    fetchAccessToken(code);
  }
}, [location]);

Explicación:

  1. generateRandomString(length):
  • Genera una cadena aleatoria de la longitud especificada (length). Puedes cambiar el tamaño dependiendo de cuán seguro quieras que sea el state, aunque 16 caracteres es suficiente para la mayoría de los casos.
  1. Almacenar el state:
  • Antes de redirigir al usuario a LinkedIn, generas y almacenas el state en localStorage. También lo envías como parte de la URL de autorización.
  1. Verificar el state:
  • Después de que LinkedIn redirija al usuario de vuelta, extraes el state de la URL y lo comparas con el que tienes almacenado. Si no coinciden, es probable que sea un ataque CSRF, por lo que deberías mostrar un mensaje de error y detener el proceso.

Recomendación:

Es recomendable borrar el state de localStorage después de que se haya usado para evitar que quede allí indefinidamente.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
localStorage.removeItem('linkedin_oauth_state');
localStorage.removeItem('linkedin_oauth_state');
localStorage.removeItem('linkedin_oauth_state');

Con esta estrategia, proteges tu aplicación contra ataques CSRF durante el proceso de autenticación OAuth.

0

Deja un comentario