Rest API refresh token problem

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

Macoun 2019 - Frühbucherrabatt bis 26.7.2019

  • Rest API refresh token problem

    Hi zusammen,

    nach einer längeren Zwangspause, Dank der Bekanntschaft meiner Hand mit einer Glasscheibe, bin ich jetzt wieder fleißig am testen und lernen und dabei auf ein Problem gestoßen, bei dem ich hoffe, dass Ihr einen Tipp für mich parat habt.

    Diese Schritte habe ich bereits abgehakt:

    1. PHP & MySQL Rest-API auf dem Server entwickelt - LÄUFT
    2. Registrierung und Login mit access token und refresh token innerhalb der IOS-App - LÄUFT
    3. Andere Daten an Rest-API senden inklusive access token - LÄUFT
    4. Andere Daten von der Rest-API lesen inklusive access token - LÄUFT
    5. Access token aktualisieren, wenn abgelaufen, per eigener Funktion refreshToken() - LÄUFT
    6. Punkt 5. innerhalb von 3. und 4. durchführen und anschließend den Rest-API-Aufruf mit neuem access token erneut abfeuern - NOPE

    Ich komme nicht weiter und es ist wie so oft zum Mäusemelken.

    Hier ein Beispiel meines Rest-API-Aufrufs:

    Quellcode

    1. let parameters = ["taskname": "\(taskname)"]
    2. let accessToken = "123456"
    3. let url = URL(string: restapiURL)!
    4. let session = URLSession.shared
    5. var request = URLRequest(url: url)
    6. request.httpMethod = "POST"
    7. do {
    8. request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
    9. } catch let error {
    10. print(error.localizedDescription)
    11. }
    12. request.addValue(accessToken, forHTTPHeaderField: "Authorization")
    13. request.addValue("application/json", forHTTPHeaderField: "Content-Type")
    14. request.addValue("application/json", forHTTPHeaderField: "Accept")
    15. let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
    16. guard error == nil else {
    17. return
    18. }
    19. guard let data = data else {
    20. return
    21. }
    22. do {
    23. let json = try JSON(data: data)
    24. if let status = json["statusCode"].int {
    25. switch status {
    26. case 201:
    27. DispatchQueue.main.async {
    28. print("ALLES BESTENS")
    29. }
    30. case 401:
    31. DispatchQueue.main.async {
    32. print("OH MIST - DER TOKEN IST WIEDER ABGELAUFEN")
    33. }
    34. default: break
    35. }
    36. }
    37. } catch let error {
    38. print(error.localizedDescription)
    39. }
    40. })
    41. task.resume()
    Alles anzeigen
    Was ich jetzt bereits versucht habe:


    1. Die Funktion refreshToken() in der Appdelegate per Timer alle 20 Minuten aufrufen (der access token läuft nach 20 Minuten ab) - Resultat wenn man die App schließt oder sie in Hintergrund kommt, bleibt der Timer stehen und somit werden die 20 Minuten überschitten. Auch mit einer zusätzlichen Abspeicherung des Startzeitpunkts und der Wiederaufnahme des Timers nicht richtig realisierbar. Probiert mit alle 5 Sekunden und sogar alle 2 aber bei vielen Nutzern wird dann irgendwann die Datenbank wohl wegfliegen.

    2. Kompletter Umbau der Rest-API-Aufrufe mit Alamofire, um "RequestAdapter" und "RequestRetrier" zu verwenden. Bin ich anscheint zu blöd zu. Nach vielen Stunden hab Ichs aufgegeben. Ich bekam immer nur einen ErrorCode 999 und einen Verbindungsfehler. Recherche im Netz hat mir gesagt, dass ich die Verbindung global verfügbar machen muss, da sie wohl zu schnell durchläuft. Probiert - gescheitert.

    3. bei jedem ViewController und TableViewController in der "viewWillAppear" die Funktion refreshToken() aufrufen. Klappt aber das wäre Glücksache. Sollte das Internet mal haken, oder der Server, dann wäre der neue access token nicht rechtzeitig da.

    4. refreshToken() vor jedem Rest-API-Aufruf starten. Also wenn z.B. der Button "Daten speichern" geklickt wird erst refreshToken() starten und dann den eigentlichen Rest-API-Aufruf. Könnte gehen nur weiß ich nicht, wie ich mit dem eigentlichen Rest-API-Aufruf warte, bis refreshToken() durch ist??

    5. unter "case 401" (in dem kleinen Code oben) die Funktion refreshToken() aufrufen. Geht und ein neuer Token ist da, aber wie starte ich jetzt den eigentliche Rest-API-Aufruf erneut, ohne die ursprünglichen Parameter immer an refreshToken() übergeben zu müssen?

    FAZIT

    Ich denke 5. ist der richtige Weg, aber ich müsste das irgendwie so lösen, dass ich nicht an refreshToken() alle ursprünglichen Parameter übergeben müsste, da innerhalb der App, jeder Rest-API-Aufruf eigene Parameter hat / benutzt / benötigt. Das würde am Ende ein Chaos geben.

    Irgendwie so:

    1. Aufruf saveDataToRestAPI(parameter1:123, parameter2: 456, parameter3: 789)
    2. Antwort 401 (token abgelaufen) > Aufruf refreshToken()
    3. refreshToken() fertig > zurück zu 1.

    Aber wie meine Freunde? Wie kann ich das umsetzen?

    Ich freue mich auf eure Ideen & Tipps und wünsche allen einen schönen Sonntag!

    LG :D
    Neu in der IOS-App-Entwicklungswelt - Spannend, ab und an nervenaufreibend, aber am Ende einfach faszinierend. Seid bitte nicht zu streng wenn ich Fragen einmal doppelt stelle, ich lerne noch :saint:

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von CodeNerd ()

  • Mac & i Test Abo
  • Ich habe in einer App auch einen Token, welcher hin und wieder refreshed werden muss. Dazu habe ich mir dann eine allgemeine POST und GET Methode erstellt, welche u.a. einen kompletten URLRequest und beim POST Daten als Parameter hat.

    Sprich, anstelle von session.dataTask() rufe ich in der Request Methode dann die POST oder GET Methode auf, welche dann den Request durchführt und den Status Code entsprechend auf 401 auswertet.

    Tritt dann der Status Code 401 auf, dann refreshe ich den Token und rufe die POST oder GET Methode rekursiv einfach erneut mit dem Request und ggf. den POST Daten erneut auf. Klappt wunderbar.

    Die POST und GET Methoden, sowie die einzelnen Request Methoden haben alle einen completionHandler, welcher dann jeweils entsprechend aufgerufen wird.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von MCDan ()

  • MCDan schrieb:

    Ich habe in einer App auch einen Token, welcher hin und wieder refreshed werden muss. Dazu habe ich mir dann eine allgemeine POST und GET Methode erstellt, welche u.a. einen kompletten URLRequest und beim POST Daten als Parameter hat.

    Sprich, anstelle von session.dataTask() rufe ich in der Request Methode fann die POST oder GET Methode auf, welche dann den Request durchführt und den Status Code entsprechend auf 401 auswertet.

    Tritt dann der Status Code 401 auf, dann refreshe ich den Token und rufe die POST oder GET Methode rekursiv einfach erneut mit dem Request und ggf. den POST Daten erneut auf. Klappt wunderbar.

    Die POST und GET Methoden, sowie die einzelnen Request Methoden haben alle einen completionHandler, welcher dann jeweils entsprechend aufgerufen wird.
    Moin MCDan,

    vielen Dank für deine Hilfe! Leider konnte ich dein Vorgehen nicht ganz nachvollziehen, aber der Begriff "completionHandler" hat mich zum Ziel geführt! Dafür schon einmal ein dickes Danke!

    Ich habe ja bereits einen completionHandler in meinem "session.dataTask" verwendet, der mir mitgeteilt hat, wenn der Request durch war. Das Ergebnis war dann ein "ok hat geklappt - sag dem User alles ist gut gegangen > 201" oder ein "Nö, dein Token will ich nicht mehr > 401".

    Kam der 401 dann hätte ein refreshToken-Request aufgerufen werden müssen und wenn dieser per completionHandler 201 zurück gibt, hätte der Ursprungs-Request inklusive aller Parameter neu gestartet werden müssen.

    Die Lösung war eigentlich so simple, aber wenn man nicht weiß wo man suchen soll und im Netz echt unglaublich viel verkompliziert wird, dann wundert es nicht, dass man früher oder später abdreht und das Handtuch wirft.

    Ich konnte das jetzt so lösen - für die, die vielleicht einmal vor dem selben Problem stehen.

    Ich hatte ja bereits eine refreshToken() Funktion ohne Parameter oder returns, die nichts anderes macht, als den refresh token an die Datenbank zu senden, um einen neuen access token anzufordern.

    In meinem Codebeispiel oben (Request an die Rest-API) hätte ich an dieser Stelle meinen Token per refreshToken() neu angefordert:

    Quellcode

    1. case 401:
    2. DispatchQueue.main.async {
    3. print("OH MIST - DER TOKEN IST WIEDER ABGELAUFEN")
    4. refreshToken()
    5. }



    Der Token wird in der Keychain von der refreshToken() Funktion gespeichert und jetzt müsste ich, sobald der Request der refreshToken() Funktion durchgelaufen wäre meinen eigentlichen Request von oben wieder neu starten. Also so:




    Quellcode

    1. case 401:
    2. DispatchQueue.main.async {
    3. print("OH MIST - DER TOKEN IST WIEDER ABGELAUFEN")
    4. refreshToken()
    5. WENN FERTIG
    6. startOldRequest(myParams)
    7. }


    Hier ging es jetzt ja darum abzuwarten, bis A refreshToken() fertig / erfolgreich ist und B den alten Request neu zu starten.

    Und so sieht das dann aus:


    Quellcode

    1. case 401:
    2. DispatchQueue.main.async {
    3. print("OH MIST - DER TOKEN IST WIEDER ABGELAUFEN")
    4. refreshToken(myCompletionHandler: { success, error in
    5. if(success){
    6. startOldRequest(myParams)
    7. }
    8. })
    9. }


    und die refreshToken() Funktion sieht jetzt so aus:


    Quellcode

    1. // STATT
    2. refreshToken(){
    3. ...
    4. 201
    5. speichere neuen Token in Keychain
    6. ...
    7. 401
    8. breche ab
    9. }
    10. // JETZT SO
    11. func refreshToken(myCompletionHandler: @escaping (Bool, Error?) -> Void) {
    12. ...
    13. 201
    14. speichere neuen Token in Keychain
    15. myCompletionHandler(true, nil)
    16. ...
    17. 401
    18. breche ab
    19. myCompletionHandler(false, nil)
    20. }
    Alles anzeigen


    Das Zauberwort war "closure" und das kannte ich bisher noch nicht. @MCDan Durch deinen Einwurf des Begriffs complitionHandler bin ich nach langer Suche darüber gestolpert und siehe da es läuft genau so wie es soll.

    Vielen Dank und eine gute Woche an dich und alle die hier mitlesen :thumbsup:
    Neu in der IOS-App-Entwicklungswelt - Spannend, ab und an nervenaufreibend, aber am Ende einfach faszinierend. Seid bitte nicht zu streng wenn ich Fragen einmal doppelt stelle, ich lerne noch :saint: