Usurpation d’identité utilisateur
Imaginez Sarah, une ingénieure de support chez TechCorp, qui reçoit un ticket urgent d'Alex, un client qui ne peut pas accéder à une ressource critique. Pour diagnostiquer et résoudre efficacement le problème, Sarah doit voir exactement ce qu'Alex voit dans le système. C'est là que la fonctionnalité d'usurpation d’identité utilisateur de Logto est utile.
L'usurpation d’identité utilisateur permet aux utilisateurs autorisés comme Sarah d'agir temporairement au nom d'autres utilisateurs comme Alex au sein du système. Cette fonctionnalité puissante est inestimable pour le dépannage, le support client et l'exécution de tâches administratives.
Comment ça fonctionne ?
Le processus d'usurpation implique trois étapes principales :
- Sarah demande l'usurpation via le serveur backend de TechCorp
- Le serveur de TechCorp obtient un jeton de sujet de la Management API de Logto
- L'application de Sarah échange ce jeton de sujet contre un jeton d’accès
Voyons comment Sarah peut utiliser cette fonctionnalité pour aider Alex.
Étape 1 : Demande d'usurpation
Tout d'abord, l'application de support de Sarah doit demander l'usurpation au serveur backend de TechCorp.
Demande (application de Sarah au serveur de TechCorp)
POST /api/request-impersonation HTTP/1.1
Host: api.techcorp.com
Authorization: Bearer <Sarah's_access_token>
Content-Type: application/json
{
"userId": "alex123",
"reason": "Investigating resource access issue",
"ticketId": "TECH-1234"
}
Dans cette API, le backend doit effectuer des vérifications d'autorisation appropriées pour s'assurer que Sarah a les permissions nécessaires pour usurper l'identité d'Alex.
Étape 2 : Obtention d'un jeton de sujet
Le serveur de TechCorp, après avoir validé la demande de Sarah, appellera ensuite la Management API de Logto pour obtenir un jeton de sujet.
Demande (serveur de TechCorp à la Management API de Logto)
POST /api/subject-tokens HTTP/1.1
Host: techcorp.logto.app
Authorization: Bearer <TechCorp_m2m_access_token>
Content-Type: application/json
{
"userId": "alex123",
"context": {
"ticketId": "TECH-1234",
"reason": "Resource access issue",
"supportEngineerId": "sarah789"
}
}
Réponse (Logto au serveur de TechCorp)
{
"subjectToken": "sub_7h32jf8sK3j2",
"expiresIn": 600
}
Le serveur de TechCorp doit ensuite retourner ce jeton de sujet à l'application de Sarah.
Réponse (serveur de TechCorp à l'application de Sarah)
{
"subjectToken": "sub_7h32jf8sK3j2",
"expiresIn": 600
}
Étape 3 : Échange du jeton de sujet contre un jeton d’accès
Maintenant, l'application de Sarah échange ce jeton de sujet contre un jeton d’accès représentant Alex, en spécifiant la ressource où le jeton sera utilisé.
Demande (application de Sarah au point de terminaison du jeton Logto)
POST /oidc/token HTTP/1.1
Host: techcorp.logto.app
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&client_id=techcorp_support_app
&scope=resource:read
&subject_token=alx_7h32jf8sK3j2
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&resource=https://api.techcorp.com/customer-data
Réponse (Logto à l'application de Sarah)
{
"access_token": "eyJhbG...<truncated>",
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "resource:read"
}
Le access_token
retourné sera lié à la ressource spécifiée, garantissant qu'il ne peut être utilisé qu'avec l'API de données client de TechCorp.
Exemple d'utilisation
Voici comment Sarah pourrait utiliser cela dans une application de support Node.js :
interface ImpersonationResponse {
subjectToken: string;
expiresIn: number;
}
interface TokenExchangeResponse {
access_token: string;
issued_token_type: string;
token_type: string;
expires_in: number;
scope: string;
}
async function impersonateUser(
userId: string,
clientId: string,
ticketId: string,
resource: string
): Promise<string> {
try {
// Étape 1 & 2 : Demande d'usurpation et obtention du jeton de sujet
const impersonationResponse = await fetch(
'https://api.techcorp.com/api/request-impersonation',
{
method: 'POST',
headers: {
Authorization: "Bearer <Sarah's_access_token>",
'Content-Type': 'application/json',
},
body: JSON.stringify({
userId,
reason: 'Investigating resource access issue',
ticketId,
}),
}
);
if (!impersonationResponse.ok) {
throw new Error(`HTTP error occurred. Status: ${impersonationResponse.status}`);
}
const { subjectToken } = (await impersonationResponse.json()) as ImpersonationResponse;
// Étape 3 : Échange du jeton de sujet contre un jeton d’accès
const tokenExchangeBody = new URLSearchParams({
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
client_id: clientId,
scope: 'openid profile resource.read',
subject_token: subjectToken,
subject_token_type: 'urn:ietf:params:oauth:token-type:access_token',
resource: resource,
});
const tokenExchangeResponse = await fetch('https://techcorp.logto.app/oidc/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: tokenExchangeBody,
});
if (!tokenExchangeResponse.ok) {
throw new Error(`HTTP error! status: ${tokenExchangeResponse.status}`);
}
const tokenData = (await tokenExchangeResponse.json()) as TokenExchangeResponse;
return tokenData.access_token;
} catch (error) {
console.error('Impersonation failed:', error);
throw error;
}
}
// Sarah utilise cette fonction pour usurper l'identité d'Alex
async function performImpersonation(): Promise<void> {
try {
const accessToken = await impersonateUser(
'alex123',
'techcorp_support_app',
'TECH-1234',
'https://api.techcorp.com/customer-data'
);
console.log('Impersonation access token for Alex:', accessToken);
} catch (error) {
console.error('Failed to perform impersonation:', error);
}
}
// Exécute l'usurpation
void performImpersonation()
- Le jeton de sujet est de courte durée et à usage unique.
- Le jeton d’accès d'usurpation ne vient pas avec un jeton de rafraîchissement. Sarah devra répéter ce processus si le jeton expire avant qu'elle ne résolve le problème d'Alex.
- Le serveur backend de TechCorp doit implémenter des vérifications d'autorisation appropriées pour s'assurer que seuls les membres du support autorisés comme Sarah peuvent demander l'usurpation.
Revendication act
Lors de l'utilisation du flux d'échange de jetons pour l'usurpation, le jeton d’accès émis peut inclure une revendication act
(acteur) supplémentaire. Cette revendication représente l'identité de la "partie agissante" - dans notre exemple, Sarah, qui effectue l'usurpation.
Pour inclure la revendication act
, l'application de Sarah doit fournir un actor_token
dans la demande d'échange de jetons. Ce jeton doit être un jeton d’accès valide pour Sarah avec la portée openid
. Voici comment l'inclure dans la demande d'échange de jetons :
POST /oidc/token HTTP/1.1
Host: techcorp.logto.app
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&client_id=techcorp_support_app
&scope=resource:read
&subject_token=alx_7h32jf8sK3j2
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&actor_token=sarah_access_token
&actor_token_type=urn:ietf:params:oauth:token-type:access_token
&resource=https://api.techcorp.com/customer-data
Si un actor_token
est fourni, le jeton d’accès résultant contiendra une revendication act
comme celle-ci :
{
"aud": "https://api.techcorp.com",
"iss": "https://techcorp.logto.app",
"exp": 1443904177,
"sub": "alex123",
"act": {
"sub": "sarah789"
}
}
Cette revendication act
indique clairement que Sarah (sarah789) agit au nom d'Alex (alex123). La revendication act
peut être utile pour l'audit et le suivi des actions d'usurpation.
Personnalisation des revendications de jetons
Logto vous permet de personnaliser les revendications de jetons pour les jetons d'usurpation. Cela peut être utile pour ajouter un contexte ou des métadonnées supplémentaires au processus d'usurpation, comme la raison de l'usurpation ou le ticket de support associé.
Lorsque le serveur de TechCorp demande un jeton de sujet à la Management API de Logto, il peut inclure un objet context
:
{
"userId": "alex123",
"context": {
"ticketId": "TECH-1234",
"reason": "Resource access issue",
"supportEngineerId": "sarah789"
}
}
Ce contexte peut ensuite être utilisé dans une fonction getCustomJwtClaims()
pour ajouter des revendications spécifiques au jeton d’accès final. Voici un exemple de la façon dont cela pourrait être implémenté :
const getCustomJwtClaims = async ({ token, context, environmentVariables }) => {
if (context.grant?.type === 'urn:ietf:params:oauth:grant-type:token-exchange') {
const { ticketId, reason, supportEngineerId } = context.grant.subjectTokenContext;
return {
impersonation_context: {
ticket_id: ticketId,
reason: reason,
support_engineer: supportEngineerId,
},
};
}
return {};
};
Le jeton d’accès résultant que Sarah reçoit pourrait ressembler à ceci :
{
"sub": "alex123",
"aud": "https://api.techcorp.com/customer-data",
"impersonation_context": {
"ticket_id": "TECH-1234",
"reason": "Resource access issue",
"support_engineer": "sarah789"
}
// ... autres revendications standard
}
En personnalisant les revendications de jetons d’accès de cette manière, TechCorp peut inclure des informations précieuses sur le contexte d'usurpation, ce qui facilite l'audit et la compréhension des activités d'usurpation dans leur système.
Soyez prudent lorsque vous ajoutez des revendications personnalisées à vos jetons. Évitez d'inclure des informations sensibles qui pourraient poser des risques de sécurité si le jeton est intercepté ou divulgué. Les JWT sont signés mais non chiffrés, donc les revendications sont visibles pour quiconque a accès au jeton.