Comment gérer les téléchargements de fichiers avec l'authentification basée JWT?
j'écris une webapp en angle où l'authentification est gérée par un JWT token, ce qui signifie que chaque requête a un en-tête" authentification " avec toutes les informations nécessaires.
cela fonctionne bien pour les appels REST, mais je ne comprends pas comment je dois gérer les liens de téléchargement pour les fichiers hébergés sur le backend (les fichiers résident sur le même serveur où les services web sont hébergés).
Je ne peux pas utiliser les liens réguliers <a href='...'/>
car ils ne porter un en-tête et l'authentification échoue. Idem pour les diverses incantations de window.open(...)
.
quelques solutions auxquelles j'ai pensé:
- générer un lien de téléchargement temporaire non sécurisé sur le serveur
- passer l'information d'authentification comme un paramètre d'url et manipuler manuellement le cas
- récupérez les données par L'intermédiaire de XHR et sauvegardez le fichier côté client.
Toutes les réponses ci-dessus sont insatisfaisantes.
1 est la solution que j'utilise actuellement. Je ne l'aime pas pour deux raisons: d'abord, il n'est pas idéal du point de vue de la sécurité, ensuite il fonctionne, mais il nécessite beaucoup de travail, en particulier sur le serveur: pour télécharger quelque chose que je dois appeler un service qui génère une nouvelle url "aléatoire", stocke quelque part (peut-être sur la base de données) pour un certain temps, et le renvoie au client. Le client obtient l'url, et utilise la fenêtre.ouvrir ou similaire. Lorsque requis, la nouvelle url doit vérifier si elle est toujours valide, puis renvoyer les données.
2 semble au moins autant de travail.
3 semble beaucoup de travail, même en utilisant les bibliothèques disponibles, et beaucoup de problèmes potentiels. (J'aurais besoin de donner mon propre barre d'état de chargement, de charger tout le fichier en mémoire, puis demander à l'utilisateur d'enregistrer le fichier localement).
la tâche semble assez basique, donc je me demande s'il y a quelque chose beaucoup plus simple que je peux utiliser.
je ne suis pas nécessairement à la recherche d'une solution "Angulaire". Javascript normal serait parfait.
4 réponses
Voici une façon de le télécharger sur le client en utilisant l'attribut de téléchargement , L'API fetch , et URL.createObjectURL . Vous récupérez le fichier en utilisant votre JWT, convertissez la charge utile en une blob, mettez la blob dans un objectURL, définissez la source d'une balise d'ancrage à cet objectURL, et cliquez sur cet objectURL en javascript.
let anchor = document.createElement("a");
let file = 'https://www.example.com/some-file.pdf';
let headers = new Headers();
headers.append('Authorization', 'Bearer MY-TOKEN');
fetch(file, { headers })
.then(response => response.blob())
.then(blobby => {
let objectUrl = window.URL.createObjectURL(blobby);
anchor.href = objectUrl;
anchor.download = 'some-file.pdf';
anchor.click();
window.URL.revokeObjectURL(objectUrl);
});
la valeur de l'attribut download
sera nom de fichier éventuel. Si vous le souhaitez, vous pouvez extraire un nom de fichier voulu de l'en-tête de réponse de disposition de contenu tel que décrit dans les autres réponses .
Technique
basé sur ce conseil de Matias Woloski de Auth0, connu évangéliste JWT, Je l'ai résolu en générant une demande signée avec Hawk .
Citant Woloski:
la façon dont vous résolvez ceci est en générant une requête signée comme le fait AWS, par exemple.
voici un exemple de cette technique, utilisée pour les liaisons d'activation.
backend
j'ai créé une API pour signer mes URL de téléchargement:
Demande:
POST /api/sign
Content-Type: application/json
Authorization: Bearer...
{"url": "https://path.to/protected.file"}
réponse:
{"url": "https://path.to/protected.file?bewit=NTUzMDYzZTQ2NDYxNzQwMGFlMDMwMDAwXDE0NTU2MzU5OThcZDBIeEplRHJLVVFRWTY0OWFFZUVEaGpMOWJlVTk2czA0cmN6UU4zZndTOD1c"}
avec une URL signée, nous pouvons obtenir le fichier
Demande:
GET https://path.to/protected.file?bewit=NTUzMDYzZTQ2NDYxNzQwMGFlMDMwMDAwXDE0NTU2MzU5OThcZDBIeEplRHJLVVFRWTY0OWFFZUVEaGpMOWJlVTk2czA0cmN6UU4zZndTOD1c
réponse:
Content-Type: multipart/mixed; charset="UTF-8"
Content-Disposition': attachment; filename=protected.file
{BLOB}
frontend (par jojoyuji )
de cette façon, vous pouvez tout faire sur un seul clic d'utilisateur:
function clickedOnDownloadButton() {
postToSignWithAuthorizationHeader({
url: 'https://path.to/protected.file'
}).then(function(signed) {
window.location = signed.url;
});
}
Je générerais des jetons à télécharger.
Dans angulaire faire une demande authentifiée pour obtenir un jeton temporaire (disons une heure), puis l'ajouter à l'url comme un paramètre get. De cette façon, vous pouvez télécharger des fichiers comme vous le voulez (fenêtre.ouvrir. ..)
une solution supplémentaire: Utiliser l'authentification de base. Bien qu'il nécessite un peu de travail sur le backend, les tokens ne seront pas visibles dans les logs et aucune signature D'URL ne devra être implémentée.
Côté Client
un exemple D'URL pourrait être:
http://jwt:<user jwt token>@some.url/file/35/download
exemple avec jeton factice:
http://jwt:eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIwIiwibmFtZSI6IiIsImlhdCI6MH0.KsKmQOZM-jcy4l_7NFsv1lWfpH8ofniVCv75ZRQrWno@some.url/file/35/download
vous pouvez alors pousser dans <a href="...">
ou window.open("...")
- le navigateur gère le reste.
Côté Serveur
L'implémentation de dépend de vous, et dépend de la configuration de votre serveur - ce n'est pas très différent de l'utilisation du paramètre de requête ?token=
.
en utilisant Laravel, j'ai suivi la voie facile et j'ai transformé le mot de passe d'authentification de base en l'en-tête JWT Authorization: Bearer <...>
, laissant l'authentification normale middleware de gérer le reste:
class CarryBasic
{
/**
* @param Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, \Closure $next)
{
// if no basic auth is passed,
// or the user is not "jwt",
// send a 401 and trigger the basic auth dialog
if ($request->getUser() !== 'jwt') {
return $this->failedBasicResponse();
}
// if there _is_ basic auth passed,
// and the user is JWT,
// shove the password into the "Authorization: Bearer <...>"
// header and let the other middleware
// handle it.
$request->headers->set(
'Authorization',
'Bearer ' . $request->getPassword()
);
return $next($request);
}
/**
* Get the response for basic authentication.
*
* @return void
* @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException
*/
protected function failedBasicResponse()
{
throw new UnauthorizedHttpException('Basic', 'Invalid credentials.');
}
}