Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss
Groups keyboard shortcuts have been updated
Dismiss
See shortcuts

Authorization: Bearer in "normalem" GET Request

5 views
Skip to first unread message

Peter J. Holzer

unread,
Aug 3, 2022, 5:56:22 AM8/3/22
to
Ich habe ein API, das Requests über ein Bearer Tokens authentifiziert.
Das funktioniert wunderbar, solange Requests über JavaScript (im
konkreten Fall mittels Axios) generiert und der Output wieder in
JavaScript konsumiert wird.

Nun möchte ich aber dem User einen Download-Link anbieten. Das wäre im
einfachsten Fall einfach ein <a href="//api.example/whatever/123">...</a>
oder man könnte window.location setzen. Aber der Browser weiß nichts von
dem Bearer-Token und setzt daher den Authorization Header nicht.

Gibt es eine Möglichkeit, den Authorization Header präemptiv zu setzen?

Alternativen:

1) Ich kann im API einen nicht-authentifizierten Endpoint einrichten,
der seinerseits einen Key (als Parameter übergeben) überprüft.
2) Ich kann den Download vom Server in JavaScript durchführen und dann
mit createObjectURL() einen blob: URL für den Inhalt generieren.

Die 1. Lösung wird schon anderswo in der gleichen Applikation verwendet,
gefällt mir aber gar nicht. Der Key müsste ein Ablaufdatum haben,
landet, im Access-Log und möglicherweise an anderen Stellen, wo er
leaken kann ...

Die 2. Lösung schaut akzeptabel aus. Zwar ist dann der Download für den
User synchron, aber im konkreten Fall sind die generierten Files klein
genug, dass das vermutlich nicht stört.

hp

Arno Welzel

unread,
Aug 3, 2022, 8:52:15 AM8/3/22
to
Peter J. Holzer, 2022-08-03 11:56:

> Ich habe ein API, das Requests über ein Bearer Tokens authentifiziert.
> Das funktioniert wunderbar, solange Requests über JavaScript (im
> konkreten Fall mittels Axios) generiert und der Output wieder in
> JavaScript konsumiert wird.
>
> Nun möchte ich aber dem User einen Download-Link anbieten. Das wäre im
> einfachsten Fall einfach ein <a href="//api.example/whatever/123">...</a>
> oder man könnte window.location setzen. Aber der Browser weiß nichts von
> dem Bearer-Token und setzt daher den Authorization Header nicht.
>
> Gibt es eine Möglichkeit, den Authorization Header präemptiv zu setzen?

Nein. Der muss schon explizit angefordert werden.

> Alternativen:
>
> 1) Ich kann im API einen nicht-authentifizierten Endpoint einrichten,
> der seinerseits einen Key (als Parameter übergeben) überprüft.
> 2) Ich kann den Download vom Server in JavaScript durchführen und dann
> mit createObjectURL() einen blob: URL für den Inhalt generieren.
>
> Die 1. Lösung wird schon anderswo in der gleichen Applikation verwendet,
> gefällt mir aber gar nicht. Der Key müsste ein Ablaufdatum haben,
> landet, im Access-Log und möglicherweise an anderen Stellen, wo er
> leaken kann ...

Sinnvoll wäre Lösung 2 nur, wenn sie Daten nutzen, die *nicht* auch vom
Server vorab ohne Authentifizierung mitgeliefert werden.



--
Arno Welzel
https://arnowelzel.de

Peter J. Holzer

unread,
Aug 5, 2022, 4:01:19 AM8/5/22
to
On 2022-08-03 12:52, Arno Welzel <use...@arnowelzel.de> wrote:
> Peter J. Holzer, 2022-08-03 11:56:
>> Ich habe ein API, das Requests über ein Bearer Tokens authentifiziert.
>> Das funktioniert wunderbar, solange Requests über JavaScript (im
>> konkreten Fall mittels Axios) generiert und der Output wieder in
>> JavaScript konsumiert wird.
>>
>> Nun möchte ich aber dem User einen Download-Link anbieten. Das wäre im
>> einfachsten Fall einfach ein <a href="//api.example/whatever/123">...</a>
>> oder man könnte window.location setzen. Aber der Browser weiß nichts von
>> dem Bearer-Token und setzt daher den Authorization Header nicht.
>>
>> Gibt es eine Möglichkeit, den Authorization Header präemptiv zu setzen?
>
> Nein. Der muss schon explizit angefordert werden.

Naja, bei Basic Authentification fragt der Browser ja auch nicht
jedesmal den User, wenn er ein 401 bekommt, sondern merkt sich die
(Hostname, Realm, Username, Passwort)-Tupel. Das könnte er auch für
Bearer Authentification machen, das Problem hier ist aber, dass der
Input dafür vom Server kommt (ein Bearer-Token ist üblicherweise ein JWT
oder sonst ein signierter Blob) und nicht vom User. Man bräuchte also
die Möglichkeit, das programmatisch entweder präenptiv oder über einen
Callback zu setzen.


>> 2) Ich kann den Download vom Server in JavaScript durchführen und dann
>> mit createObjectURL() einen blob: URL für den Inhalt generieren.
>>
> Sinnvoll wäre Lösung 2 nur, wenn sie Daten nutzen, die *nicht* auch vom
> Server vorab ohne Authentifizierung mitgeliefert werden.

Ja, natürlich. Wenn die Daten öffentlich wären, wönnte das Backend sie
auch ohne Authentifizierung ausliefern.

Der Code sieht jetzt übrigens so aus:

axios.get(endpoint, options)
.then((res) => {
const blob = new Blob([res.data], {type: res.headers["content-type"]})
const url = window.URL.createObjectURL(blob);
const link = document.getElementById("download");
link.href = url;
link.click();
})

Ein <a id="download"></a> muss irgendwo auf der Seite bereits
existieren.

Arno Welzel

unread,
Aug 5, 2022, 4:46:42 AM8/5/22
to
Peter J. Holzer, 2022-08-05 10:01:

> On 2022-08-03 12:52, Arno Welzel <use...@arnowelzel.de> wrote:
>> Peter J. Holzer, 2022-08-03 11:56:
>>> Ich habe ein API, das Requests über ein Bearer Tokens authentifiziert.
>>> Das funktioniert wunderbar, solange Requests über JavaScript (im
>>> konkreten Fall mittels Axios) generiert und der Output wieder in
>>> JavaScript konsumiert wird.
>>>
>>> Nun möchte ich aber dem User einen Download-Link anbieten. Das wäre im
>>> einfachsten Fall einfach ein <a href="//api.example/whatever/123">...</a>
>>> oder man könnte window.location setzen. Aber der Browser weiß nichts von
>>> dem Bearer-Token und setzt daher den Authorization Header nicht.
>>>
>>> Gibt es eine Möglichkeit, den Authorization Header präemptiv zu setzen?
>>
>> Nein. Der muss schon explizit angefordert werden.
>
> Naja, bei Basic Authentification fragt der Browser ja auch nicht
> jedesmal den User, wenn er ein 401 bekommt, sondern merkt sich die
> (Hostname, Realm, Username, Passwort)-Tupel. Das könnte er auch für
> Bearer Authentification machen, das Problem hier ist aber, dass der
> Input dafür vom Server kommt (ein Bearer-Token ist üblicherweise ein JWT
> oder sonst ein signierter Blob) und nicht vom User. Man bräuchte also
> die Möglichkeit, das programmatisch entweder präenptiv oder über einen
> Callback zu setzen.

Genau das meine ich ja - man kann den Authorization Header nicht senden,
ohne ihn vorher vom Server anzufordern.

[...]
> Der Code sieht jetzt übrigens so aus:
>
> axios.get(endpoint, options)
> .then((res) => {
> const blob = new Blob([res.data], {type: res.headers["content-type"]})
> const url = window.URL.createObjectURL(blob);
> const link = document.getElementById("download");
> link.href = url;
> link.click();
> })

D.h. jeder Angreifer kann sich den Authorization Header einfach anhand
des frei zugänglichen Code in seinem Browser per Web Developer-Console
einfach abgreifen? Oder ist das Code, den nur Benutzer zu sehen
bekommen, die sich vorher schon anderweitig authentifizieren mussten?

Peter J. Holzer

unread,
Aug 5, 2022, 7:48:09 AM8/5/22
to
On 2022-08-05 08:46, Arno Welzel <use...@arnowelzel.de> wrote:
> Peter J. Holzer, 2022-08-05 10:01:
>> Naja, bei Basic Authentification fragt der Browser ja auch nicht
>> jedesmal den User, wenn er ein 401 bekommt, sondern merkt sich die
>> (Hostname, Realm, Username, Passwort)-Tupel. Das könnte er auch für
>> Bearer Authentification machen, das Problem hier ist aber, dass der
>> Input dafür vom Server kommt (ein Bearer-Token ist üblicherweise ein JWT
>> oder sonst ein signierter Blob) und nicht vom User. Man bräuchte also
>> die Möglichkeit, das programmatisch entweder präenptiv oder über einen
>> Callback zu setzen.
>
> Genau das meine ich ja - man kann den Authorization Header nicht senden,
> ohne ihn vorher vom Server anzufordern.

Äh, den Authorization Header fordert der Server an, indem er mit 401
antwortet. Der Browser sendet dann den Request ein zweites Mal,
inklusive Authorization Header. Die Frage ist, wo kommen die Daten für
den Authorization Header her? Bei Basic Authorization kommen sie aus
einem Dialog (der vielleicht wieder vom Passwort-Manager ausgefüllt
wird). Bei einem Bearer Token ist das nicht praktikabel, das muss vom
Programmcode gesetzt werden können.

>> Der Code sieht jetzt übrigens so aus:
>>
>> axios.get(endpoint, options)
>> .then((res) => {
>> const blob = new Blob([res.data], {type: res.headers["content-type"]})
>> const url = window.URL.createObjectURL(blob);
>> const link = document.getElementById("download");
>> link.href = url;
>> link.click();
>> })
>
> D.h. jeder Angreifer kann sich den Authorization Header einfach anhand
> des frei zugänglichen Code in seinem Browser per Web Developer-Console
> einfach abgreifen? Oder ist das Code, den nur Benutzer zu sehen
> bekommen, die sich vorher schon anderweitig authentifizieren mussten?

Ich verstehe die Frage nicht ganz. Der Benutzer loggt sich ein, und
bekommt ein Token. Dieses Token wird bei jedem Request in Form eines
Authorization Headers mitgeschickt. Das Code-Snippet hier wird
ausgeführt, wenn der User schon eingeloggt ist.

Ein anderer User bekommt natürlich nicht das gleiche Token und ein
Angreifer sollte das auch nie zu Gesicht bekommen. Außer der Angriff
besteht darin, dass wer in der Mittagspause nach unversperrten PCs sucht
und dann mittels Developer-Console die Tokens aus dem Browser (mit der
eingeloggten Applikation) ausliest. Wenn er das macht, hat er bis zum
Ablaufdatum des Tokens genau die gleichen Rechte wie der Browser, aus
dem er das Token ausgelesen hat, ja. Aber dafür braucht er schon Zugang
zum PC und wenn er den hat, kann er vermutlich schlimmere Dinge
anstellen.

hp

Arno Welzel

unread,
Aug 6, 2022, 4:35:54 AM8/6/22
to
Peter J. Holzer, 2022-08-05 13:48:

> On 2022-08-05 08:46, Arno Welzel <use...@arnowelzel.de> wrote:
>> Peter J. Holzer, 2022-08-05 10:01:
>>> Naja, bei Basic Authentification fragt der Browser ja auch nicht
>>> jedesmal den User, wenn er ein 401 bekommt, sondern merkt sich die
>>> (Hostname, Realm, Username, Passwort)-Tupel. Das könnte er auch für
>>> Bearer Authentification machen, das Problem hier ist aber, dass der
>>> Input dafür vom Server kommt (ein Bearer-Token ist üblicherweise ein JWT
>>> oder sonst ein signierter Blob) und nicht vom User. Man bräuchte also
>>> die Möglichkeit, das programmatisch entweder präenptiv oder über einen
>>> Callback zu setzen.
>>
>> Genau das meine ich ja - man kann den Authorization Header nicht senden,
>> ohne ihn vorher vom Server anzufordern.
>
> Äh, den Authorization Header fordert der Server an, indem er mit 401
> antwortet. Der Browser sendet dann den Request ein zweites Mal,

Dann reden wir von zwei verschiedenen Dingen. Ich dachte, es ging um das
Token, was per API angefordert wird und *danach* als Authorization
Header gesendet wird - also jedesmal etwas anderes.

[...]
> wird). Bei einem Bearer Token ist das nicht praktikabel, das muss vom
> Programmcode gesetzt werden können.

Eben - das meinte ich ja!

>>> Der Code sieht jetzt übrigens so aus:
>>>
>>> axios.get(endpoint, options)
>>> .then((res) => {
>>> const blob = new Blob([res.data], {type: res.headers["content-type"]})
>>> const url = window.URL.createObjectURL(blob);
>>> const link = document.getElementById("download");
>>> link.href = url;
>>> link.click();
>>> })
>>
>> D.h. jeder Angreifer kann sich den Authorization Header einfach anhand
>> des frei zugänglichen Code in seinem Browser per Web Developer-Console
>> einfach abgreifen? Oder ist das Code, den nur Benutzer zu sehen
>> bekommen, die sich vorher schon anderweitig authentifizieren mussten?
>
> Ich verstehe die Frage nicht ganz. Der Benutzer loggt sich ein, und

Die Frage ist, ob der oben gezeigte Code *ohne* vorherige Anmeldung
benutzt wird oder nicht.

> bekommt ein Token. Dieses Token wird bei jedem Request in Form eines
> Authorization Headers mitgeschickt. Das Code-Snippet hier wird
> ausgeführt, wenn der User schon eingeloggt ist.

Ja - das meinte ich. Wenn der Code ohnehin erst dann *nach* Anmeldung
benutzt wird, dann ist es selbstverständlich kein Problem.

Peter J. Holzer

unread,
Aug 6, 2022, 8:06:17 AM8/6/22
to
On 2022-08-06 08:35, Arno Welzel <use...@arnowelzel.de> wrote:
> Peter J. Holzer, 2022-08-05 13:48:
>> On 2022-08-05 08:46, Arno Welzel <use...@arnowelzel.de> wrote:
>>> Peter J. Holzer, 2022-08-05 10:01:
>>>> Naja, bei Basic Authentification fragt der Browser ja auch nicht
>>>> jedesmal den User, wenn er ein 401 bekommt, sondern merkt sich die
>>>> (Hostname, Realm, Username, Passwort)-Tupel. Das könnte er auch für
>>>> Bearer Authentification machen, das Problem hier ist aber, dass der
>>>> Input dafür vom Server kommt (ein Bearer-Token ist üblicherweise ein JWT
>>>> oder sonst ein signierter Blob) und nicht vom User. Man bräuchte also
>>>> die Möglichkeit, das programmatisch entweder präenptiv oder über einen
>>>> Callback zu setzen.
>>>
>>> Genau das meine ich ja - man kann den Authorization Header nicht senden,
>>> ohne ihn vorher vom Server anzufordern.
>>
>> Äh, den Authorization Header fordert der Server an, indem er mit 401
>> antwortet. Der Browser sendet dann den Request ein zweites Mal,
>
> Dann reden wir von zwei verschiedenen Dingen. Ich dachte, es ging um das
> Token, was per API angefordert wird und *danach* als Authorization
> Header gesendet wird - also jedesmal etwas anderes.

Mit hat verwirrt, dass Du "den Authorization Header ... anfordern"
geschrieben hast. Denn den *Header* fordert der Client eben nicht an.
Und das Token hat er zu diesem Zeitpunkt längst, es geht nur darum, dem
Browser beizubringen, es auch zu verwenden.

Insgesamt war mir also ziemlich unklar, worauf Du hinauswillst.


>> bekommt ein Token. Dieses Token wird bei jedem Request in Form eines
>> Authorization Headers mitgeschickt. Das Code-Snippet hier wird
>> ausgeführt, wenn der User schon eingeloggt ist.
>
> Ja - das meinte ich. Wenn der Code ohnehin erst dann *nach* Anmeldung
> benutzt wird, dann ist es selbstverständlich kein Problem.

Wenn er vorher benutzt werden könnte, hätte sich meine Frage erübrigt,
denn dann gäbe es ja keinen Grund, sich gegenüber dem Backend zu
authentifizieren.

hp

Stefan Reuther

unread,
Aug 6, 2022, 4:51:34 PM8/6/22
to
Am 03.08.2022 um 11:56 schrieb Peter J. Holzer:
> Gibt es eine Möglichkeit, den Authorization Header präemptiv zu setzen?
>
> Alternativen:
>
> 1) Ich kann im API einen nicht-authentifizierten Endpoint einrichten,
> der seinerseits einen Key (als Parameter übergeben) überprüft.
> 2) Ich kann den Download vom Server in JavaScript durchführen und dann
> mit createObjectURL() einen blob: URL für den Inhalt generieren.
>
> Die 1. Lösung wird schon anderswo in der gleichen Applikation verwendet,
> gefällt mir aber gar nicht. Der Key müsste ein Ablaufdatum haben,
> landet, im Access-Log und möglicherweise an anderen Stellen, wo er
> leaken kann ...

Muss es denn ein GET sein? Das Log/Leak-Problem lässt sich auch umgehen,
indem der Key als Feldwert in ein skriptgeneriertes Formular gepackt
wird. Dann erscheint er in keinem der üblichen Logs und leakt nicht
versehentlich.

Die Sicherheit verbessert das aber nur unwesentlich. Der Mensch, der
einen Link mit Auth-Parameter kopieren und archivieren kann, kann auch
in den Entwickler-Tools einen Request mit Authorization-Header als
curl-Kommando kopieren und archivieren. Den Auth-Parameter mit
Ablaufdatum zu versehen wäre daher keine schlechte Idee.

Je nachdem, was für Daten du da mit Ablaufdatum von 5 Minuten zum
Download anbietest, muss der Download-Endpunkt dann nicht mal den Inhalt
der ursprünglichen Authorization kennen ("wer ist der User?"), sondern
nur wissen, *dass* der User korrekt autorisiert war. Wenn ich das
richtig in Erinnerung habe, arbeiten CDNs von Streamingdiensten so.


Stefan

Peter J. Holzer

unread,
Aug 7, 2022, 3:39:42 AM8/7/22
to
On 2022-08-06 20:28, Stefan Reuther <stefa...@arcor.de> wrote:
> Am 03.08.2022 um 11:56 schrieb Peter J. Holzer:
>> Gibt es eine Möglichkeit, den Authorization Header präemptiv zu setzen?
>>
>> Alternativen:
>>
>> 1) Ich kann im API einen nicht-authentifizierten Endpoint einrichten,
>> der seinerseits einen Key (als Parameter übergeben) überprüft.
[...]
>> Die 1. Lösung wird schon anderswo in der gleichen Applikation verwendet,
>> gefällt mir aber gar nicht. Der Key müsste ein Ablaufdatum haben,
>> landet, im Access-Log und möglicherweise an anderen Stellen, wo er
>> leaken kann ...
>
> Muss es denn ein GET sein? Das Log/Leak-Problem lässt sich auch umgehen,
> indem der Key als Feldwert in ein skriptgeneriertes Formular gepackt
> wird. Dann erscheint er in keinem der üblichen Logs und leakt nicht
> versehentlich.

Stimmt. Ich könnte auch ein Formular mit method=POST und ein paar hidden
Fields einrichten. Daran hatte ich in diesem Zusammenhang gar nicht gedacht.
Liegt vermutlich daran, dass ich bezüglich der Semantik von GET/POST/PUT/
DELETE ein bisschen ein Purist bin und unpassende Methoden nur ungern
und im Notfall verwende.


> Die Sicherheit verbessert das aber nur unwesentlich. Der Mensch, der
> einen Link mit Auth-Parameter kopieren und archivieren kann, kann auch
> in den Entwickler-Tools einen Request mit Authorization-Header als
> curl-Kommando kopieren und archivieren.

Ja. Im konkreten Fall würde ich aber nicht erwarten, dass das passiert.
Ich dachte eher, dass jemand den Link per Mail verschickt oder einen
Blog-Post einbaut, oder dass er in Matomo-Auswertungen auftaucht.

hp
0 new messages