3.2.1 Authentifizierung Native Applikation
Um ein Sage ID Access Token für eine Native Applikation wie Mobile oder Desktop Applikation zu erhalten, muss OAuth 2.0 Grant Flow mit Proof Key for Code Exchange (RFC 7636) implementiert werden.
Mit PKCE, eine Applikation erzeugt für jede Anfrage eine kryptographisch zufälligen Key, den code_verifier
und transformiert einen Parameter code_challenge
, der an Sage ID gesendet wir, um den authorization_code
zu erhalten. Wenn eine Applikation den authorization_code
erhält, wird dieser code und code_verifier
an den Sage ID Token Endpunkt gesendet, um im Austausch die anfragten Token (Access Token) zu erhalten.
Dieser OAuth 2.0 Flow hat folgende Schritte:
Ihre Applikation initiiert den Flow und leitet einen Benutzer an Sage ID (
/authorize
Endpunkt) weiter und sendetcode_challenge
undcode_challenge_method
Parameter.SageID leitet den Benutzer zu Ihrer Applikation weiter mit einem
authorization_code
Parameter in der URL.Ihre Applikation sendet
authorization_code
undcode_verifier
zusammen mitredirect_uri
undclient_id
an Sage ID zum/oauth/token
Endpunkt.Sage ID validiert diese Parameter und gibt ein Access Token und optional ein Refresh Token zurück an Ihre Applikation.
Ihre Applikation speichert auf sichere Art das Refresh Token unter Verwendung des Betriebssystem z.B. Data Protection (DPAPI) pro Benutzer.
Dann kann Ihre Applikation das Access Token verwenden, um Sage 100 API aufzurufen im Benutzerkontext.
Wenn das Access Token abläuft, kann Ihrer Applikation den Sage ID
/oauth/token
Endpunkt aufrufen mitrefresh_token
Grant Typ und dem Refresh Token, um ein neues Access Token zu erhalten.
Wenn ein Benutzer sich abmeldet von Ihrer Applikation, muss das Refresh Token gelöscht werden.
OAuth 2.0 Flow
Folgende Postman Collection enthält OAuth 2.0 Flows für die Native Applikation mit ClientID:
Für die anderen Typen muss ein Sage ID Client beantragt werden.
Sichere Speicherung von Refresh Token in einer Native Applikation
Refresh Tokens müssen auf sichere Art und Weise gespeichert werden, weil den Namen und Passwort eines Benutzers repräsentieren. Mit einem gültigen Refresh Token kann ein Access Token geholt werden. Ein Refresh Token kann z.B. 90 Tage gültig sein. Falls Access Token. Falls ein Refresh Token nicht geschützt wird und ein Angreifer Zugriff erhält, kann im Austausch ein Access Token erzeugt werden und dieser im Benutzerkontext Anfragen stellen.
In Windows ist es eine bewährte Methode DPAPI in user mode zu verwenden und diese zu speichern mit einem zufälligen AES Schlüssel. Dieser Schlüssel kann verwendet werden, um eine Datei zu verschlüsseln, der den Refresh Token enthält. Es ist empfohlen die Datei in einem isolierten Speicher in dem Benutzerprofil zu speichern. NTFS Berechtigungen können gesetzt werden, um diese zusätzlich zu schützen.
Implementierung
In diesem Kapitel werden die Schritte beschrieben, um eine SageID Anmeldung in einer .Net Anwendung umzusetzen. Ein code verifier und eine code challenge wird erzeugt, um nach einem Browser login eines Benutzers ein Access token zu erhalten und eine Anfrage an die Sage 100 API umsetzen zu können. Anschließend wird mit einem Refresh Token ein aktualisiertes Access Token erhalten.
Die Code Snippets sollten in .Net Framework und auch .Net Core funktionieren mit RestSharp Nuget Package. Diese Code Snippets sind einfach gehalten und dienen den Flow zu veranschaulichen. Es kann natürlich auch andere Packages verwendet werden als RestSharp für die Anfragen.
1. Erzeugen eine Code Verifier
Zunächst wird ein code_verifier
gespeichert.
var rng = RandomNumberGenerator.Create();
var bytes = new byte[32];
rng.GetBytes(bytes);
// Ein URL-safe string sollte verwendet werden als code_verifier (section 4 of RFC 7636)
var verifier = Convert.ToBase64String(bytes)
.TrimEnd('=')
.Replace('+', '-')
.Replace('/', '_');
2. Erzeugen einer Code Challenge
code_verifier
wird verwendet, um eine code_challenge
zu erzeugen, die an den /authorize
Endpunkt gesendet wird.
Sie müssen diesen Wert Hashen und dürfen diesen nicht im Klartext senden.
var challenge = string.Empty;
using (var sha256 = SHA256.Create())
{
var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(verifier));
challenge = Convert.ToBase64String(challengeBytes)
.TrimEnd('=')
.Replace('+', '-')
.Replace('/', '_');
3. Erhalten der Benutzer Token
Um den Authorization Code Flow zu starten, muss die Native Applikation den Benutzer zur /authorize
Endpunkt weiterleiten inklusive der code_challenge
.
Bei der Implementierung sollte nicht ein Prozess zum Starten eines Browsers wie im simplen Beispiel gestartet werden. Es könnte z.B. ein WPF Browser Control verwendet werden für die SageID Anmeldung und auslesen des Authorization Code.
//Beispiel zum Öffnen eines Browser Fensters und erhalten des Codes
string url = "https://id.sage.com/authorize?audience=s100de/sage100&scope=openid token access_token offline_access email profile&response_type=code&client_id=<IHRE_CLIENTID>&code_challenge=<IHRE_CODE_CHALLENGE>&code_challenge_method=S256&redirect_uri=https://id.sage.com/mobile";
ProcessStartInfo psi = new ProcessStartInfo
{
FileName = url,
UseShellExecute = true
};
Process.Start(psi);
4. Austausch des Authorization Code mit einem Access Token
Jetzt kann der Authorization Code ausgetauscht werden mit einem Access Token, um einen Aufruf der Sage 100 API umsetzen zu können. Der Authorization Code vom vorhergehenden Schritt wird verwendet, der an /oauth/token
per POST gesendet werden kann.
var client = new RestSharp.RestClient();
var request = new RestRequest(new Uri("https://id.sage.com/oauth/token"), Method.Post);
request.RequestFormat = DataFormat.Json;
request.AddJsonBody(new
{
grant_type = "authorization_code",
client_id = "IHR_CLIENT_ID",
code_verifier = "IHR_GENERIERTER_CODE_VERIFIER",
code = "IHR_AUTHORIZATION_CODE",
redirect_uri = "https://id.sage.com/mobile"
});
RestResponse response = await client.ExecuteAsync(request);
The Antwort enthält access_token
, refresh_token
, id_token
, und token_type
. Ein Beispiel:
{
"access_token":"eyJhbGc...XkRIg",
"refresh_token":"cqd...KwKfc",
"id_token":"eyJhb...gupw",
"scope":"openid profile email offline_access",
"expires_in":28800,
"token_type":"Bearer"
}
Der refresh_token wird nur zurückgegeben mit offline_access scope.
5. Aufruf der Sage 100 API
Mit dem access_token
kann die Sage 100 API aufgerufen werden, in dem der Access Token (Bearer) übergeben wird im Authorization Header.
var client = new RestSharp.RestClient();
var request = new RestRequest(new Uri("https://connectivity.sage.de/ws/<IHRE_ENTITLEMENTID>/sdata/<IHRE_SDATA_URL>"), Method.Get);
request.AddHeader("Authorization", "Bearer <Aktueller_Access_Token>");
request.AddHeader("X-Sage-ConnectivityVersion", "1.3");
//weitere Http Header, z.B.
//request.AddHeader("X-ApiEndpoint", "ept<Ihr Endpunkt>");
//...
RestResponse response = await client.ExecuteAsync(request);
return response;
6. Einen neuen Access Token anfragen
Wenn ein refresh_token
erhalten wurde, kann der Endpunkt /oauth/token
angefragt werden mit dem refresh_token
grant_type und dem refresh token, um ein neuen Access Token zu erhalten.
var client = new RestSharp.RestClient();
var request = new RestRequest(new Uri("https://id.sage.com/oauth/token"), Method.Post);
request.RequestFormat = DataFormat.Json;
request.AddJsonBody(new
{
grant_type = "refresh_token",
refresh_token = "IHR_REFRESH_TOKEN",
client_id = "IHR_CLIENT_ID"
});
RestResponse response = await client.ExecuteAsync(request);
Die Antwort enthält access_token
, id_token
, scope
, expires_in
und token_type
.