Como lidar com downloads de arquivos com autenticação baseada em JWT?
Estou escrevendo um webapp em Angular em que a autenticação é controlada por um token JWT, o que significa que cada solicitação tem um cabeçalho de "Autenticação" com todas as informações necessárias.
Isso funciona bem para chamadas REST, mas não entendo como devo lidar com links de download para arquivos hospedados no back-end (os arquivos residem no mesmo servidor onde os serviços da web estão hospedados).
Não posso usar <a href='...'/>links regulares, pois eles não carregam nenhum cabeçalho e a autenticação falhará. O mesmo vale para os vários encantamentos de window.open(...).
Gerar um link de download não seguro temporário no servidor
Passe as informações de autenticação como um parâmetro de url e lide manualmente com o caso
Obtenha os dados por meio de XHR e salve o arquivo do lado do cliente.
Todos os itens acima são menos que satisfatórios.
1 é a solução que estou usando agora. Não gosto disso por dois motivos: primeiro não é o ideal em termos de segurança, segundo funciona, mas requer bastante trabalho, especialmente no servidor: para baixar algo, preciso chamar um serviço que gera um novo "aleatório "url, armazena em algum lugar (possivelmente no banco de dados) por algum tempo e o devolve ao cliente. O cliente obtém o url e usa window.open ou semelhante com ele. Quando solicitado, o novo url deve verificar se ainda é válido e, em seguida, retornar os dados.
2 parece pelo menos tanto trabalho.
3 parece muito trabalhoso, mesmo usando as bibliotecas disponíveis, e muitos problemas potenciais. (Eu precisaria fornecer minha própria barra de status de download, carregar o arquivo inteiro na memória e então pedir ao usuário para salvar o arquivo localmente).
A tarefa parece bastante básica, então estou me perguntando se há algo muito mais simples que eu possa usar.
Não estou necessariamente procurando uma solução "do jeito Angular". Javascript regular seria bom.
— Marco Righele
Remoto, você quer dizer que os arquivos para download estão em um domínio diferente do aplicativo Angular? Você controla o controle remoto (tem acesso para modificar o backend) ou não?
— robertjd
Quero dizer que os dados do arquivo não estão no cliente (navegador); o arquivo está hospedado no mesmo domínio e eu tenho controle do back-end. Vou atualizar a pergunta para torná-la menos ambígua.
A dificuldade da opção 2 depende do seu back-end. Se você puder dizer ao seu back-end para verificar a string de consulta, além do cabeçalho de autorização para o JWT quando ele passar pela camada de autenticação, está feito. Qual back-end você está usando?
— Tecnécio,
Esta é uma maneira de baixá-lo no cliente usando o atributo download , a API fetch e URL.createObjectURL . Você deve buscar o arquivo usando seu JWT, converter a carga útil em um blob, colocar o blob em um objectURL, definir a origem de uma tag âncora para esse objectURL e clicar nesse objectURL em javascript.
O valor do downloadatributo será o nome do arquivo eventual. Se desejar, você pode extrair um nome de arquivo pretendido do cabeçalho de resposta de disposição de conteúdo, conforme descrito em outras respostas .
— Tecnécio
Eu fico me perguntando por que ninguém considera essa resposta. É simples e como estamos vivendo em 2017, o suporte da plataforma é bastante bom.
Mas o suporte iosSafari para o atributo de download parece bem vermelho :(
— Martin Cremer
Isso funcionou bem para mim no cromo. Para o firefox funcionou depois que adicionei a âncora ao documento: document.body.appendChild (anchor); Não encontrou nenhuma solução para Edge ...
— Tompi
Esta solução funciona, mas ela lida com questões de UX com arquivos grandes? Se às vezes eu precisar baixar um arquivo de 300 MB, pode levar algum tempo para fazer o download antes de clicar no link e enviá-lo para o gerenciador de download do navegador. Poderíamos nos esforçar para usar a API fetch-progress e construir nossa própria IU de progresso do download ... mas também há a prática questionável de carregar um arquivo de 300 MB no js-land (na memória?) Para simplesmente transferi-lo para o download Gerente.
— scvnc
@Tompi Eu também não consegui fazer isso funcionar para o Edge e o IE
Com base neste conselho de Matias Woloski de Auth0, conhecido evangelista do JWT, resolvi o problema gerando um pedido assinado com Hawk .
Citando Woloski:
A maneira de resolver isso é gerando uma solicitação assinada como a AWS, por exemplo.
Aqui você tem um exemplo dessa técnica, usada para links de ativação.
Eu criei uma API para assinar meus urls de download:
Com um URL assinado, podemos obter o arquivo
frontend (por jojoyuji )
Dessa forma, você pode fazer tudo com um único clique do usuário:
— Ezequias Dinella
Isso é legal, mas não entendo como é diferente, de uma perspectiva de segurança, da opção # 2 do OP (token como parâmetro de string de consulta). Na verdade, posso imaginar que a solicitação assinada poderia ser mais restritiva, ou seja, apenas com permissão para acessar um determinado endpoint. Mas o OP # 2 parece mais fácil / menos etapas, o que há de errado nisso?
Dependendo do seu servidor web, o URL completo pode ser registrado em seus arquivos de log. Você pode não querer que seu pessoal de TI tenha acesso a todos os tokens.
Além disso, o URL com a string de consulta seria salvo no histórico do usuário, permitindo que outros usuários da mesma máquina acessassem o URL.
Finalmente, o que torna isso muito inseguro é que a URL é enviada no cabeçalho Referer de todas as solicitações de qualquer recurso, mesmo de recursos de terceiros. Portanto, se estiver usando o Google Analytics, por exemplo, você enviará ao Google o token de URL e tudo para eles.
Este texto foi retirado daqui: stackoverflow.com/questions/643355/…
Uma alternativa às abordagens existentes "fetch / createObjectURL" e "download-token" já mencionadas é um Form POST padrão que visa uma nova janela . Assim que o navegador ler o cabeçalho do anexo na resposta do servidor, ele fechará a nova guia e iniciará o download. Essa mesma abordagem também funciona bem para exibir um recurso como um PDF em uma nova guia.
Isso oferece melhor suporte para navegadores mais antigos e evita a necessidade de gerenciar um novo tipo de token. Isso também terá um suporte de longo prazo melhor do que a autenticação básica no URL, uma vez que o suporte para nome de usuário / senha no url está sendo removido pelos navegadores .
No lado do cliente , usamostarget="_blank" para evitar a navegação mesmo em casos de falha, o que é particularmente importante para SPAs (aplicativos de página única).
A principal ressalva é que a validação do JWT do lado do servidor deve obter o token dos dados POST e não do cabeçalho . Se sua estrutura gerencia o acesso aos roteadores de rota automaticamente usando o cabeçalho de autenticação, pode ser necessário marcar seu manipulador como não autenticado / anônimo para que possa validar manualmente o JWT para garantir a autorização adequada.
O formulário pode ser criado dinamicamente e imediatamente destruído para que seja devidamente limpo (observação: isso pode ser feito em JS simples, mas JQuery é usado aqui para maior clareza) -
function DownloadWithJwtViaFormPost(url, id, token) {
var jwtInput = $('<input type="hidden" name="jwtToken">').val(token);
var idInput = $('<input type="hidden" name="id">').val(id);
$('<form method="post" target="_blank"></form>')
.append(jwtInput)
.append(idInput)
Basta adicionar quaisquer dados extras que você precisa enviar como entradas ocultas e certifique-se de que eles sejam anexados ao formulário.
Acredito que essa solução é muito desvalorizada. É fácil, limpo e funciona perfeitamente.
— Yura Fedoriv
Gostaria de gerar tokens para download.
No angular, faça uma solicitação autenticada para obter um token temporário (digamos uma hora) e adicione-o ao url como um parâmetro get. Desta forma, você pode baixar arquivos da maneira que quiser (window.open ...)
Esta é a solução que estou usando agora, mas não estou satisfeito com ela porque é muito trabalhoso e espero que haja uma solução melhor "lá fora" ...
Acho que esta é a solução mais limpa disponível e não consigo ver muito trabalho lá. Mas eu escolheria um menor tempo de validade do token (por exemplo, 3 minutos) ou torná-lo um token único, mantendo uma lista dos tokens no servidor e excluindo os tokens usados ​​(não aceitando tokens que não estão na minha lista )
— nabinca
Uma solução adicional: usando autenticação básica. Embora exija um pouco de trabalho no back-end, os tokens não serão visíveis nos logs e nenhuma assinatura de URL terá que ser implementada.
Um exemplo de URL poderia ser:
Exemplo com token fictício:
Você pode então enfiar isso <a href="...">ou window.open("...")- o navegador cuida do resto.
A implementação aqui depende de você e depende da configuração do seu servidor - não é muito diferente de usar o ?token=parâmetro de consulta.
Usando o Laravel, peguei o caminho mais fácil e transformei a senha de autenticação básica no Authorization: Bearer <...>cabeçalho JWT , deixando o middleware de autenticação normal cuidar do resto:
— AlbinoDrought
Essa abordagem parece promissora, mas não vejo uma maneira de obter acesso ao token JWT dessa forma. Você pode me indicar algum recurso de como o servidor analisa esse URL estranho e onde acessar o valor do token jwt?