{"id":55000414,"date":"2024-02-01T00:00:00","date_gmt":"2024-02-25T19:10:19","guid":{"rendered":"http:\/\/access-im-unternehmen.aix-dev.de\/aiu\/?p=414"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-30T00:00:00","slug":"GoogleAuthentifizierung_mit_OAuth2_Update","status":"publish","type":"post","link":"https:\/\/vbentwickler.de\/GoogleAuthentifizierung_mit_OAuth2_Update\/","title":{"rendered":"Google-Authentifizierung mit OAuth2, Update"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/vg08.met.vgwort.de\/na\/aa3953fbc19a42bbae8494d35358edc3\" width=\"1\" height=\"1\" alt=\"\"><\/p>\n<p><b>In den beiden Artikeln &#8220;OAuth2-Token f&uuml;r Google per .NET-App holen&#8221; (www.vbentwickler.de\/413) und &#8220;Google-Token per DLL holen&#8221; (www.vbentwickler.de\/409) haben wir Techniken beschrieben, mit denen wir ein Google OAuth2-Token ermitteln k&ouml;nnen, das wir f&uuml;r den Zugriff auf die Google Rest API per VBA ben&ouml;tigen. Dazu haben wir das NuGet-Paket <b>Google.Apis.Calendar.v3 <\/b>verwendet. Leider funktionierte das Ermitteln des Access-Tokens mit dem Refresh-Token nicht wie gew&uuml;nscht. Also stellen wir in diesem Artikel eine Erweiterung der Projekte aus den vorgenannten Artikeln vor, mit denen wir den Zugriff immer erneuern k&ouml;nnen &#8211; wenn auch jeweils auf Kosten einer erneuten Anmeldung &uuml;ber den Webbrowser.<\/b><\/p>\n<p>Wie erhielten, wenn wir per VBA die mit den oben genannten L&ouml;sungen ermittelten Access-Token f&uuml;r den Zugriff auf die Google Calendar-API genutzt haben, in vielen F&auml;llen die Meldung, dass Token nicht mehr g&uuml;ltig sei (siehe Bild 1).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2024_01\/pic_414_003.png\" alt=\"Fehlermeldung bei fehlgeschlagener Authentifizierung\" width=\"499,6267\" height=\"459,7049\"\/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 1: Fehlermeldung bei fehlgeschlagener Authentifizierung<\/span><\/b><\/p>\n<p>Andererseits erlaubten uns die in den beiden Projekten verwendeten Versuche der Authorisierung &uuml;ber die Methode <b>AuthorizeAsync <\/b>der Klasse <b>GoogleWebAuthorizationBroker <\/b>nicht, das Token zu erneuern oder dieses erneut freizuschalten.<\/p>\n<p>Nach einer Weile intensiven Suchens haben wir im Internet herausgefunden, dass diese Klasse eine Datei im folgenden Verzeichnis anlegt:<\/p>\n<pre>C:\\Users\\[Benutzername]\\AppData\\Roaming\\Google.Apis.Auth<\/pre>\n<p>Diese Datei hei&szlig;t <b>Google.Apis.Auth.OAuth2.Responses.TokenResponse-user <\/b>und sieht wie in Bild 2 aus.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2024_01\/pic_414_001.png\" alt=\"Die Datei mit den aktuellen Authentifizierungsdaten\" width=\"549,6265\" height=\"301,2219\"\/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 2: Die Datei mit den aktuellen Authentifizierungsdaten<\/span><\/b><\/p>\n<p>Erst, wenn wir diese l&ouml;schen, erhalten wir die M&ouml;glichkeit, uns mit dem Webdialog aus Bild 3 erneut zu authentifizieren.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2024_01\/pic_414_002.png\" alt=\"Erneute Authentifizierung nach dem L&ouml;schen der Datei\" width=\"649,627\" height=\"418,7249\"\/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 3: Erneute Authentifizierung nach dem L&ouml;schen der Datei<\/span><\/b><\/p>\n<p>Danach k&ouml;nnen wir wieder f&uuml;r eine Stunde mit diesen Zugangsdaten arbeiten, danach m&uuml;ssen wir uns erneut authentifizieren.<\/p>\n<h2>Herausfinden, ob das Konto noch verbunden ist<\/h2>\n<p>Wenn wir das Konto so ausgew&auml;hlt und den Zugriff gestattet haben, k&ouml;nnen wir mit den VBA-Routinen, die wir zum Beispiel im Artikel <b>Google Calendar per Rest-API programmieren <\/b>(<b>www.vbentwickler.de\/410<\/b>) vorgestellt haben, auf den Kalender des Benutzers zugreifen, in dessen Kontext wir den Zugriff freigeschaltet haben.<\/p>\n<p>Manchmal gelingt dies nicht, und wir k&ouml;nnen dann die Verbindung f&uuml;r dieses Konto zuerst l&ouml;schen und dann wieder herstellen. Dazu m&uuml;ssen wir erst einmal wissen, wo man einsieht, ob f&uuml;r dieses Konto &uuml;berhaupt noch eine Verbindung besteht.<\/p>\n<p>Dazu geben wir im Browser die folgende URL ein:<\/p>\n<pre>https:\/\/myaccount.google.com\/<\/pre>\n<p>Auf der nun erscheinenden Seite w&auml;hlen wir links den Bereich <b>Sicherheit <\/b>aus und scrollen dann nach unten zu <b>Ihre Verbindungen zu Drittanbieter-Apps und -diensten <\/b>(siehe Bild 4).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2024_01\/pic_414_005.png\" alt=\"Der Bereich Sicherheit im Google-Benutzerkonto\" width=\"700\" height=\"340,9293\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 4: Der Bereich Sicherheit im Google-Benutzerkonto<\/span><\/b><\/p>\n<p>Hier erscheint bereits der Eintrag namens <b>amvGoogleCalender<\/b>, den wir untersuchen wollen. Sollte dieser nicht direkt sichtbar sein, klicken wir unten auf <b>Alle Verbindungen ansehen<\/b>.<\/p>\n<p>Danach klicken wir auf den Eintrag <b>amvGoogleCalendar<\/b>. Es erscheint die Anzeige aus Bild 5. Hier sehen wir zwei M&ouml;glichkeiten:<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2024_01\/pic_414_006.png\" alt=\"Details zur App amvGoogleCalendar\" width=\"700\" height=\"384,0648\"\/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 5: Details zur App amvGoogleCalendar<\/span><\/b><\/p>\n<ul>\n<li>Anzeige von Details &uuml;ber die Zugriffsberechtigungen<\/li>\n<li>L&ouml;schen der Verbindungen mit der App <b>amvGoogleCalendar<\/b><\/li>\n<\/ul>\n<p>Wenn wir die erste M&ouml;glichkeit nutzen und auf die Schaltfl&auml;che <b>Details ansehen <\/b>klicken, finden wir die Seite aus Bild 6 vor. Hier sehen wir wichtige Informationen, zum Beispiel wann der Zugriff gew&auml;hrt wurde &#8211; also wann das Access-Token freigeschaltet wurde. Wir k&ouml;nnen noch einen Bereich aufklappen, der uns anzeigt, welche Berechtigungen wir der App genau freigeben. Wir k&ouml;nnen aber auch hier auf Zugriff entfernen klicken, was dazu f&uuml;hrt, dass eine Meldung mit den Konsequenzen angezeigt wird. In diesem Fall werden alle Berechtigungen der App f&uuml;r unseren Kalender entzogen. Wir k&ouml;nnen dies nur wiederherstellen, indem wir das Access-Token erneut generieren.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2024_01\/pic_414_007.png\" alt=\"Details &uuml;ber die Zugriffsberechtigungen\" width=\"700\" height=\"391,2999\"\/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 6: Details &uuml;ber die Zugriffsberechtigungen<\/span><\/b><\/p>\n<p>Entfernen wir die Zugriffsrechte f&uuml;r die App und versuchen anschlie&szlig;end, mit den VBA-Prozeduren auf den Google-Kalender zuzugreifen, erhalten wir wieder die Antwort, dass wir mit ung&uuml;ltigen Credentials zugegriffen haben.<\/p>\n<h2>Erneut anmelden<\/h2>\n<p>Rufen wir nun beispielsweise die Funktion <b>GetTokens <\/b>aus der VBA-L&ouml;sung auf, l&auml;uft diese ohne Probleme durch und zeigt uns eine Meldung mit den ermittelten Daten, in diesem Fall dem Access-Token und dem Refresh-Token (siehe Bild 7).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2024_01\/pic_414_008.png\" alt=\"Meldung mit den ermittelten Zugriffsdaten\" width=\"499,6267\" height=\"381,0712\"\/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 7: Meldung mit den ermittelten Zugriffsdaten<\/span><\/b><\/p>\n<p>Allerdings erscheint kein Webbrowser mit der Abfrage des zu verwendenden Kontos und der Best&auml;tigung des Zugriffs durch die App <b>amvGoogleCalendar <\/b>auf dieses Konto. Das hei&szlig;t nichts Gutes, denn dieser Schritt sollte nach dem Entfernen und dem Erneuern der Berechtigungen erfolgen.<\/p>\n<p>Hier ist nun das oben beschriebene Problem aufgetreten: Dadurch, dass die Datei <b>Google.Apis.Auth.OAuth2.Responses.TokenResponse-user <\/b>noch im Ordner <b>C:\\Users\\User\\AppData\\Roaming\\Google.Apis.Auth <\/b>gespeichert ist, nimmt die DLL an, das dies noch g&uuml;ltige Zugangsdaten sind.<\/p>\n<p>Wir m&uuml;ssen also an irgendeiner Stelle das L&ouml;schen oder Umbenennen dieser Datei integrieren.<\/p>\n<p>Wir integrieren dies in die Prozedur <b>GetTokens<\/b> der Beispieldatenbank <b>GoogleCalendar2<\/b>. Als Erstes m&uuml;ssen wir dazu herausfinden, wie wir zuverl&auml;ssig auf das Verzeichnis zugreifen, in dem sich die Datei <b>Google.Apis.Auth.OAuth2.Responses.TokenResponse-user <\/b>befindet.<\/p>\n<p>Dies ist das Anwendungsverzeichnis im Verzeichnis des aktuellen Benutzers. Um dieses zu erhalten, nutzen wir die Funktion <b>Environ <\/b>mit dem Parameter <b>AppData<\/b>. An dieses h&auml;ngen wir das Verzeichnis <b>Google.Apis.Auth <\/b>an und dahinter den Dateinamen der zu l&ouml;schenden Datei. Den L&ouml;schvorgang mit der <b>Kill<\/b>-Anweisung fassen wir noch in <b>On Error Resume Next <\/b>und <b>On Error Goto 0 <\/b>ein.<\/p>\n<p>Damit verhindern wir, dass ein Fehler ausgel&ouml;st wird, wenn die Datei noch gar nicht oder nicht mehr vorhanden ist. Die neue Version der Routine <b>GetTokens<\/b> sieht wie in Listing 1 aus.<\/p>\n<pre><span style=\"color:blue;\">Public Function <\/span>GetTokens()<span style=\"color:blue;\"> As Boolean<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strClientID<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strClientSecret<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strRefreshToken<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strAccessToken<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>lngExpiresInSeconds<span style=\"color:blue;\"> As Long<\/span>\r\n     strClientID = GetAppSetting(\"ClientID\")\r\n     <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Len<\/span>(strClientID) = 0<span style=\"color:blue;\"> Then<\/span>\r\n         <span style=\"color:blue;\">MsgBox<\/span> \"Keine ClientID in der Registry gefunden. Verwende die Prozedur SetClientData.\", vbOKOnly + _\r\n             vbExclamation, \"ClientID fehlt\"\r\n         <span style=\"color:blue;\">Exit Function<\/span>\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     strClientSecret = GetAppSetting(\"ClientSecret\")\r\n     <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Len<\/span>(strClientSecret) = 0<span style=\"color:blue;\"> Then<\/span>\r\n         <span style=\"color:blue;\">MsgBox<\/span> \"Kein ClientSecret in der Registry gefunden. Verwende die Prozedur SetClientData.\", vbOKOnly + _\r\n             vbExclamation, \"ClientSecret fehlt\"\r\n         <span style=\"color:blue;\">Exit Function<\/span>\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     On Error Resume <span style=\"color:blue;\">Next<\/span>\r\n     Kill Environ(\"AppData\") & \"\\Google.Apis.Auth\\Google.Apis.Auth.OAuth2.Responses.TokenResponse-user\"\r\n     <span style=\"color:blue;\">On Error GoTo<\/span> 0\r\n     <span style=\"color:blue;\">If <\/span>GetTokensDLL(strClientID, strClientSecret, strRefreshToken, strAccessToken, lngExpiresInSeconds)<span style=\"color:blue;\"> Then<\/span>\r\n         SaveAppSetting \"RefreshToken\", strRefreshToken\r\n         SaveAppSetting \"AccessToken\", strAccessToken\r\n         SaveAppSetting \"TokenExpiresAt\", DateAdd(\"s\", lngExpiresInSeconds, Now)\r\n         GetTokens = <span style=\"color:blue;\">True<\/span>\r\n     <span style=\"color:blue;\">End If<\/span>\r\n<span style=\"color:blue;\">End Function<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 1: Die Prozedur GetTokens holt die Token f&uuml;r die ClientID und das ClientSecret.<\/span><\/b><\/p>\n<p>Diese haben wir au&szlig;erden noch in eine Funktion umgewandelt, die den Wert <b>True <\/b>zur&uuml;ckgibt, wenn die Funktion <b>GetTokensDLL <\/b>erfolgreich ausgef&uuml;hrt wurde.<\/p>\n<h2>Access-Token erneuern oder beide Token neu erstellen<\/h2>\n<p>Nachdem wir nun die Prozedur <b>GetTokens <\/b>so aktualisiert haben, dass diese automatisch die Datei mit den aktuellen Zugangsdaten l&ouml;scht, damit diese neu erstellt und freigegeben werden kann, wollen wir auch noch die Prozedur <b>RefreshAccessToken <\/b>so erweitern, dass diese pr&uuml;ft, ob das Access-Token erneuert werden konnte (siehe Listing 2).<\/p>\n<pre><span style=\"color:blue;\">Public Function <\/span>RefreshAccessToken()<span style=\"color:blue;\"> As Boolean<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strClientID<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strClientSecret<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>lngExpiresIn<span style=\"color:blue;\"> As Long<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strRefreshToken<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strAccessToken<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>lngStatus<span style=\"color:blue;\"> As Long<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strErrorTitle<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strErrorDescription<span style=\"color:blue;\"> As String<\/span>\r\n     \r\n     strClientID = GetAppSetting(\"ClientID\")\r\n     strClientSecret = GetAppSetting(\"ClientSecret\")\r\n     strRefreshToken = GetAppSetting(\"RefreshToken\")\r\n         \r\n     If RefreshAccessTokenRestAPI(strClientID, strClientSecret, strRefreshToken, strAccessToken, lngExpiresIn, _\r\n             lngStatus, strErrorTitle, strErrorDescription) = <span style=\"color:blue;\">True<\/span> Then\r\n         SaveAppSetting \"AccessToken\", strAccessToken\r\n         SaveAppSetting \"TokenExpiresAt\", DateAdd(\"s\", lngExpiresIn, Now)\r\n         RefreshAccessToken = <span style=\"color:blue;\">True<\/span>\r\n     <span style=\"color:blue;\">Else<\/span>\r\n         Select Case strErrorTitle\r\n             <span style=\"color:blue;\">Case <\/span>\"invalid_grant\"\r\n                 If <span style=\"color:blue;\">MsgBox<\/span>(\"Authentifizierungstoken nicht mehr g&uuml;ltig. Erneuern?\", vbYesNo + vbExclamation, _\r\n                         \"Token ung&uuml;ltig\") = vbYes Then\r\n                     <span style=\"color:blue;\">If <\/span>GetTokens = <span style=\"color:blue;\">True<\/span><span style=\"color:blue;\"> Then<\/span>\r\n                         RefreshAccessToken = <span style=\"color:blue;\">True<\/span>\r\n                     <span style=\"color:blue;\">End If<\/span>\r\n                 <span style=\"color:blue;\">End If<\/span>\r\n             <span style=\"color:blue;\">Case Else<\/span>\r\n                 <span style=\"color:blue;\">MsgBox<\/span> \"Status: \" & lngStatus & <span style=\"color:blue;\">vbCrLf<\/span> & <span style=\"color:blue;\">vbCrLf<\/span> & \"Fehler: \" & strErrorTitle & <span style=\"color:blue;\">vbCrLf<\/span> & <span style=\"color:blue;\">vbCrLf<\/span> _\r\n                     & \"Beschreibung: \" & strErrorDescription, <span style=\"color:blue;\">vbCr<\/span>itical + vbOKOnly, \"Fehler bei Refresh Token\"\r\n         <span style=\"color:blue;\">End Select<\/span>\r\n     <span style=\"color:blue;\">End If<\/span>\r\n<span style=\"color:blue;\">End Function<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 2: Erneuern des Access-Tokens oder beider Token<\/span><\/b><\/p>\n<p>Ist dies nicht der Fall, sollen beide Token, also Refresh-Token und Access-Token, erneuert werden.<\/p>\n<p>Das erledigen wir durch den Aufruf der soeben beschriebenen Prozedur <b>GetTokens<\/b>, allerdings nur in einem bestimmten Fall.<\/p>\n<p>Dazu haben wir der Funktion <b>RefreshAccessToken<\/b> eine <b>Select Case<\/b>-Bedingung im <b>Else<\/b>-Teil der <b>If&#8230;Then<\/b>-Bedingung hinzugef&uuml;gt, die pr&uuml;ft, ob <b>RefreshAccessTokenRestAPI <\/b>erfolgreich war.<\/p>\n<p>War diese nicht erfolgreich, folgt im <b>Else<\/b>-Zweig die Select Case-Anweisung, die den Inhalt des Parameters <b>strErrorTitle <\/b>mit dem Titel des Fehlers pr&uuml;ft. Lautet dieser auf <b>invalid_grant<\/b>, zeigt die Funktion eine Meldung an, in welcher der Benutzer entscheiden kann, ob er beide Token komplett neu erstellen und freigeben lassen m&ouml;chte. Ist das der Fall, rufen wir die oben beschriebene Funktion <b>GetTokens <\/b>auf. Sofern diese den Wert <b>True <\/b>zur&uuml;ckliefert, stellen wir auch den R&uuml;ckgabewert der Funktion <b>RefreshAccessToken <\/b>ebenfalls auf den Wert <b>True <\/b>ein.<\/p>\n<p>Dazu haben wir aus der Prozedur <b>CheckToken<\/b> ebenfalls eine Funktion gemacht, die nun wie in Listing 3 aussieht.<\/p>\n<pre><span style=\"color:blue;\">Public Function <\/span>CheckToken()<span style=\"color:blue;\"> As Boolean<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>datTokenExpiresAt<span style=\"color:blue;\"> As Date<\/span>\r\n     <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Len<\/span>(GetAppSetting(\"TokenExpiresAt\")) = 0<span style=\"color:blue;\"> Then<\/span>\r\n         datTokenExpiresAt = 0\r\n     <span style=\"color:blue;\">Else<\/span>\r\n         datTokenExpiresAt = GetAppSetting(\"TokenExpiresAt\")\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     <span style=\"color:blue;\">If <\/span>datTokenExpiresAt &lt; Now<span style=\"color:blue;\"> Then<\/span>\r\n         <span style=\"color:blue;\">If <\/span>RefreshAccessToken<span style=\"color:blue;\"> Then<\/span>\r\n             CheckToken = <span style=\"color:blue;\">True<\/span>\r\n         <span style=\"color:blue;\">End If<\/span>\r\n     <span style=\"color:blue;\">Else<\/span>\r\n         CheckToken = <span style=\"color:blue;\">True<\/span>\r\n     <span style=\"color:blue;\">End If<\/span>\r\n<span style=\"color:blue;\">End Function<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 3: Pr&uuml;fen des Tokens<\/span><\/b><\/p>\n<h2>Erweiterung der App zum Erneuern der Token<\/h2>\n<p>Auch die App, die wir im Artikel <b>OAuth2-Token f&uuml;r Google per .NET-App holen <\/b>(<b>www.vbentwickler.de\/413<\/b>) vorgestellt haben, haben wir etwas &uuml;berarbeitet.<\/p>\n<p>Als Erstes haben wir eine weitere Schaltfl&auml;che hinzugef&uuml;gt, die den Test der Verbindung per Rest-API mit den aktuell angezeigten Daten erlaubt. Diese zeigt nun entweder an, dass der Test erfolgreich war, oder liefert eine Fehlermeldung wie in Bild 8.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2024_01\/pic_414_009.png\" alt=\"Erweiterung der amvGetGoogleToken-App um eine Funktion zum Testen der Authentifizierung\" width=\"700\" height=\"406,0867\"\/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 8: Erweiterung der amvGetGoogleToken-App um eine Funktion zum Testen der Authentifizierung<\/span><\/b><\/p>\n<p>Hinter dieser Schaltfl&auml;che verbirgt sich prinzipiell die gleiche Funktion, die wir auch in der Access-Datenbank <b>GoogleCalendar2.accdb <\/b>verwenden. Ein wichtiger Unterschied: Unter VB.NET m&uuml;ssen wir Parameter, die Werte zur&uuml;ckliefern sollen, explizit als <b>ByRef <\/b>deklarieren.<\/p>\n<p>Daneben haben wir die Prozedur erweitert, die durch die Schaltfl&auml;che zum Aktualisieren der Token dient (<b>btnRefresh<\/b>) &#8211; siehe Listing 4.<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>btnRefresh_Click(sender<span style=\"color:blue;\"> As Object<\/span>, e<span style=\"color:blue;\"> As <\/span>RoutedEventArgs)\r\n     <span style=\"color:blue;\">Dim <\/span>objCalendarService<span style=\"color:blue;\"> As <\/span>Google.Apis.Calendar.v3.CalendarService\r\n     <span style=\"color:blue;\">Dim <\/span>objClientSecrets<span style=\"color:blue;\"> As <\/span><span style=\"color:blue;\">New<\/span> ClientSecrets\r\n     <span style=\"color:blue;\">Dim <\/span>objCredential<span style=\"color:blue;\"> As <\/span>Google.Apis.Auth.OAuth2.UserCredential\r\n     <span style=\"color:blue;\">Dim <\/span>objInitializer<span style=\"color:blue;\"> As <\/span>BaseClientService.Initializer\r\n     <span style=\"color:blue;\">Dim <\/span>intCode<span style=\"color:blue;\"> As Integer<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strResponse<span style=\"color:blue;\"> As String<\/span> = \"\"\r\n     <span style=\"color:blue;\">Dim <\/span>strTokenFile<span style=\"color:blue;\"> As String<\/span> = \"\"\r\n     <span style=\"color:blue;\">With<\/span> objClientSecrets\r\n         .ClientId = txtClientID.Text\r\n         .ClientSecret = txtClientSecret.Text\r\n     End <span style=\"color:blue;\">With<\/span>\r\n     Try\r\n         objCredential = GoogleWebAuthorizationBroker.AuthorizeAsync(objClientSecrets, _\r\n             {CalendarService.Scope.Calendar}, \"user\", CancellationToken.None).Result\r\n         objInitializer = <span style=\"color:blue;\">New<\/span> BaseClientService.Initializer\r\n         objInitializer.HttpClientInitializer = objCredential\r\n         objCalendarService = <span style=\"color:blue;\">New<\/span> CalendarService(objInitializer)\r\n         txtRefreshToken.Text = objCredential.Token.RefreshToken\r\n         txtAccessToken.Text = objCredential.Token.AccessToken\r\n         txtExpiresAt.Text = DateAdd(DateInterval.Second, CLng(objCredential.Token.ExpiresInSeconds), Now)\r\n     Catch ex<span style=\"color:blue;\"> As <\/span>Exception\r\n         <span style=\"color:blue;\">MsgBox<\/span>(\"Ausnahme: \" & ex.Message)\r\n     End Try\r\n     <span style=\"color:blue;\">If <\/span>TestRequest(strResponse, intCode) = <span style=\"color:blue;\">True<\/span><span style=\"color:blue;\"> Then<\/span>\r\n         <span style=\"color:blue;\">MsgBox<\/span>(\"Token erneuert.\", vbOKOnly + vbExclamation, \"amvGetGoogleToken\")\r\n     <span style=\"color:blue;\">Else<\/span>\r\n         <span style=\"color:blue;\">MsgBox<\/span>(\"Token nicht erneuert.\" & <span style=\"color:blue;\">vbCrLf<\/span> & <span style=\"color:blue;\">vbCrLf<\/span> & strResponse, vbOKOnly + <span style=\"color:blue;\">vbCr<\/span>itical, \"amvGetGoogleToken\")\r\n         strTokenFile = Environ(\"AppData\") & \"\\Google.Apis.Auth\\Google.Apis.Auth.OAuth2.Responses.TokenResponse-user\"\r\n         <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Not<\/span> <span style=\"color:blue;\">Len<\/span>(Dir(strTokenfile)) = 0<span style=\"color:blue;\"> Then<\/span>\r\n             If <span style=\"color:blue;\">MsgBox<\/span>(\"Dies kann daran liegen, dass die folgende Datei erst gel&ouml;scht werden muss:\" & <span style=\"color:blue;\">vbCrLf<\/span> & <span style=\"color:blue;\">vbCrLf<\/span> _\r\n                     & strTokenFile & <span style=\"color:blue;\">vbCrLf<\/span> & <span style=\"color:blue;\">vbCrLf<\/span> & \"L&ouml;schen und erneut versuchen?\", vbYesNo + vbExclamation, _\r\n                     \"amvGetGoogleTokens\") = vbYes Then\r\n                 Try\r\n                     Kill(strTokenFile)\r\n                     btnRefresh_Click(sender, e)\r\n                 Catch\r\n                 End Try\r\n             <span style=\"color:blue;\">End If<\/span>\r\n         <span style=\"color:blue;\">End If<\/span>\r\n     <span style=\"color:blue;\">End If<\/span>\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 4: Aktualisieren der Token<\/span><\/b><\/p>\n<p>Dieser Prozedur haben wir hinter dem eigentlichen Vorgang zum Aktualisieren des Tokens einen Aufruf der Prozedur <b>TestRequest <\/b>hinzugef&uuml;gt, die einen einfachen Aufruf an die Google Rest-API sendet. Wenn diese den Wert <b>True <\/b>zur&uuml;ckliefert, war der Aufruf erfolgreich und das Token kann nun verwendet werden.<\/p>\n<p>Wenn nicht, erscheint eine Meldung mit dem Hinweis, dass das Token nicht erneut erstellt und autorisiert werden konnte. Zus&auml;tzlich wird die von der Rest-API zur&uuml;ckgelieferte Fehlermeldung &uuml;bergeben. In einer zweiten Meldung fragt die App den Benutzer, ob dieser die Datei <b>Google.Apis.Auth.OAuth2.Responses.TokenResponse-user <\/b>l&ouml;schen m&ouml;chte.<\/p>\n<p>Stimmt der Benutzer dem zu, versucht die Prozedur, die Datei mit der <b>Kill<\/b>-Anweisung zu l&ouml;schen und ruft sich anschlie&szlig;end erneut selbst auf.<\/p>\n<h2>Zusammenfassung und Ausblick<\/h2>\n<p>Wir haben in diesem Artikel untersucht, ob und wann die Authentifizierung nicht mehr funktioniert und warum diese nicht einfach erneuert werden kann, wenn sie nicht mehr funktioniert.<\/p>\n<p>Der Grund ist, dass die NuGet-Bibliothek <b>Google.Apis.Calendar.v3<\/b> zum Erstellen des Tokens eine Datei mit den resultierenden Informationen speichert, die bei sp&auml;teren Versuchen der Authentifizierung gepr&uuml;ft wird. Wenn diese Datei noch vorhanden ist, geht die Bibliothek offensichtlich davon aus, dass auch die Authentifizierung mit dem aktuell gespeicherten Access-Token noch funktioniert.<\/p>\n<p>Deshalb haben wir sowohl die VBA-Prozedur in der Access-Datenbank <b>amvGoogleCalendar2.accdb <\/b>als auch das .NET-Tool zum Erstellen der Token so erweitert, dass diese die Datei vor dem erneuten Autorisieren eines Tokens l&ouml;scht und dann erst die Neuerstellung der Token anst&ouml;&szlig;t.<\/p>\n<p>Mit diesen optimierten Prozeduren k&ouml;nnen wir nun weitere M&ouml;glichkeiten der Rest-API f&uuml;r den Google Calendar untersuchen als auch konkrete L&ouml;sungen programmieren &#8211; beispielsweise, um Termine von Outlook per Mausklick an den Google-Kalender zu &uuml;bertragen oder auch Eintr&auml;ge aus dem Google-Kalender nach Outlook zu importieren. Dies schauen wir uns in weiteren Artikeln an.<\/p>\n<h2>Downloads zu diesem Beitrag<\/h2>\n<p>Enthaltene Beispieldateien:<\/p>\n<p>amvGoogleOAuth2.zip<\/p>\n<p><a href=\"..\/fileadmin\/beispiele\/A0609F53-1AF0-42F4-B73C-1F51A36DF12F\/vbe_414.zip\">Download<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In den beiden Artikeln &#8220;OAuth2-Token f&uuml;r Google per .NET-App holen&#8221; (www.vbentwickler.de\/413) und &#8220;Google-Token per DLL holen&#8221; (www.vbentwickler.de\/409) haben wir Techniken beschrieben, mit denen wir ein Google OAuth2-Token ermitteln k&ouml;nnen, das wir f&uuml;r den Zugriff auf die Google Rest API per VBA ben&ouml;tigen. Dazu haben wir das NuGet-Paket Google.Apis.Calendar.v3 verwendet. Leider funktionierte das Ermitteln des Access-Tokens mit dem Refresh-Token nicht wie gew&uuml;nscht. Also stellen wir in diesem Artikel eine Erweiterung der Projekte aus den vorgenannten Artikeln vor, mit denen wir den Zugriff immer erneuern k&ouml;nnen &#8211; wenn auch jeweils auf Kosten einer erneuten Anmeldung &uuml;ber den Webbrowser.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"footnotes":""},"categories":[66012024,662024,44000012,44000023,44000036],"tags":[],"yst_prominent_words":[],"class_list":["post-55000414","post","type-post","status-publish","format-standard","hentry","category-66012024","category-662024","category-Interaktiv","category-PowerApps","category-Ribbon_programmieren"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/posts\/55000414","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/comments?post=55000414"}],"version-history":[{"count":0,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/posts\/55000414\/revisions"}],"wp:attachment":[{"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/media?parent=55000414"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/categories?post=55000414"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/tags?post=55000414"},{"taxonomy":"yst_prominent_words","embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/yst_prominent_words?post=55000414"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}