Jira-Authentifizierung mit OAuth 1.0a

In meinem letzten Artikel haben wir uns angesehen, wie sich die REST-API von Jira ansprechen lässt, um aus unserer eigenen Software heraus ein Jira-Issue anzulegen. Die Authentifizierung an Jira fand dabei mit einem fixen Benutzer statt, dessen Benutzername und Passwort fest hinterlegt wurden. Alle Aktionen im Jira wurden mit diesem festen Benutzer ausgeführt. Das hat den Nachteil, dass alle Anwender sich denselben Jira-User teilen und dadurch das Berechtigungssystem von Jira ausgehebelt wird. Viel praktischer und sicherer wäre es, wenn ein Jira-Issue mit dem jeweiligen Jira-User des Anwenders angelegt werden würde. Mit der OAuth-Schnittstelle von Jira lässt sich genau das erreichen.

Der OAuth-Workflow

Zur Authentifizierung wird von Jira derzeit lediglich OAuth 1.0a unterstützt. OAuth ist ein token-basierter Authentifizierungsstandard: Nach erfolgreicher Anmeldung mit OAuth wird ein Access-Token bereitgestellt, welches den angemeldeten User identifiziert. Alle Operationen, die mit diesem Access-Token stattfinden, finden im Namen des entsprechenden Benutzers statt. Ein solches Token ist nur bis zu einem bestimmten Zeitpunkt gültig, danach muss erneut eine Authentifizierung stattfinden. In Jira beträgt die Lebensdauer eines Access-Tokens standardmäßig fünf Jahre.

Der eigentliche Prozess zur Authentifizierung besteht aus mehreren Requests, die gegen verschiedene Endpunkte der Jira-API gehen:

Request-Token URL        /plugins/servlet/oauth/request-token

Authorization URL           /plugins/servlet/oauth/authorize

Access-Token URL           /plugins/servlet/oauth/access-token

 

Der grobe Ablauf sieht wie folgt aus:

  1. Über die Request-Token URL wird zunächst ein neues Request-Token abgerufen
  2. Dieses Request-Token wird an die Authorization URL angehängt und die so entstandene URL im Browser geöffnet
  3. Im Browser wird dem Anwender nun angezeigt, dass die Anwendung in seinem Namen auf Jira zugreifen möchte. Der User hat hier nun die Möglichkeit, den Zugriff zuzulassen oder zu verweigern
  4. Mittels der Access-Token URL wird nun geprüft, ob der Anwender den Zugriff für das zuvor verwendete Request-Token gestattet hat
  5. Falls ja wird ein Access-Token mitsamt zugehörigem Access-Token-Secret und Ablaufdatum zurückgegeben. Die Anmeldung war erfolgreich.

Jira Application Links

Ehe die Authentifizierung mit OAuth überhaupt möglich ist, muss die Client-Anwendung zunächst im Jira registriert werden. Hierfür bietet Jira sogenannte Application Links an. Mit einem solchen Application Link wird einer Client-Anwendung ein bestimmter Schlüssel zugeordnet, den der Client bei jeder Anfrage mitschicken muss. Zudem muss ein RSA-Schlüsselpaar erzeugt werden: Die Requests des Clients werden mit dem privaten Schlüssel signiert und im Jira mit dem öffentlichen Schlüssel gegengeprüft. Nur wenn die Signatur übereinstimmt und der Request einem bestimmten Application Link zugeordnet werden kann, ist eine Authentifizierung überhaupt möglich. Andernfalls wird der Request von der API abgelehnt.

Im Jira Application Link sind folgende Informationen enthalten:

  • Consumer Name: Der Name der Anwendung, der dem Anwender beim Anmeldeprozess angezeigt wird
  • Consumer Key: Eindeutiger Schlüssel der Client-Anwendung
  • Public Key: Öffentlicher Schlüssel, mit dem die OAuth-Signatur der eingehenden Requests geprüft wird

 

Über den Consumer Key findet die Zuordnung zum Application Link statt. Clientseitig benötigen wir also diese Werte:

  • Consumer Key: Eindeutiger Schlüssel der Client-Anwendung
  • Private Key: Privater Schlüssel, der zur OAuth-Signatur der Requests verwendet wird (in der OAuth-Spezifikation „Consumer Secret“ genannt)

Sobald der Application Link erzeugt wurde kann die Authentifizierung stattfinden.

Sicherheitsaspekte

Unabhängig von der konkreten Implementierung der Oauth-Authentifizierung muss bedacht werden, dass es sich beim Private Key bzw. Consumer Secret um eine sensible Information handelt. Gelangt ein potentieller Angreifer in den Besitz des Consumer Secret, so könnte er sich gegenüber Jira als eine registrierte Anwendung ausgeben und den angelegten Application Link nutzen. Gerade bei Client-Anwendungen lässt es sich leider nicht verhindern, dass jemand das Consumer Secret auslesen könnte.  Dennoch sollte man es möglichen Angreifern so schwer wie möglich machen und sich der Gefahren bewusst sein. In der OAuth-Spezifikation (Abschnitt 11.7) heißt es hierzu:

“In many applications, the Consumer application will be under the control of potentially untrusted parties. For example, if the Consumer is a freely available desktop application, an attacker may be able to download a copy for analysis. In such cases, attackers will be able to recover the Consumer Secret used to authenticate the Consumer to the Service Provider. Accordingly, Service Providers should not use the Consumer Secret alone to verify the identity of the Consumer. Where possible, other factors such as IP address should be used as well.”

Implementierung der Authentifizierung

Nun müssen wir nur noch den oben beschriebenen OAuth-Workflow entsprechend umsetzen. Der Einfachheit halber verwenden wir RestSharp, welches bereits Unterstützung für OAuth1 mitbringt. In diesem Beispiel erzeugen wir einen neuen RestClient, über den die Authentifizierung läuft. Während des Workflows tauschen wir dann einfach immer wieder den Authenticator des RestClients aus, um das entsprechende Token abzurufen. Zunächst müssen wir wie oben beschrieben das Request-Token anfragen:

Die Response enthält einen Query-String, in dem unter anderem auch das Request-Token vorhanden ist. Dieses hängen wir nun an die Authorization URL als Parameter „oauth_token“ an. Die so entstandene URL muss nun im Browser geöffnet werden, damit der Anwender sich dort an Jira anmelden und unserer Anwendung den Zugriff auf Jira in seinem Namen gestatten kann. Da dieser Vorgang asynchron außerhalb unserer Client-Anwendung stattfindet bietet es sich an, ihn auszulagern. In diesem Beispiel wird das über die Func<string,bool> authorizeUser geregelt, die von außen reingegeben wird:

Was genau in authorizeUser passiert kann je nach Anwendungsfall unterschiedlich sein. Wichtig ist nur, dass der Authentifizierungsvorgang im Browser gestartet wird. Über den Rückgabewert können wir entscheiden, ob der Vorgang vom Anwender beispielsweise abgebrochen wurde.

Sobald die Authentifizierung erfolgt ist können wir fortfahren und prüfen, ob der Anwender tatsächlich das Request-Token durch den Anmeldevorgang bestätigt hat. Hierfür erzeugen wir einen neuen Authenticator für unseren RestClient, diesmal jedoch um das Access-Token abzurufen:

Auch hier enthält die Response wieder einen Query-String mit allen wichtigen Informationen. Diese können wir dann direkt in einen neuen Authenticator übernehmen:

Und das war es schon! Unser RestClient ist nun gegenüber Jira authentifiziert. Alle weiteren Requests finden mit dem zuvor abgerufenen Access-Token statt und damit im Namen des Anwenders. Wir können den RestClient nun für alle API-Calls verwenden, bis das Access-Token abläuft oder auf andere Art und Weise ungültig wird. Erst dann muss die Authentifizierung erneut stattfinden.