Refresh session token of Auth0 with RxSwift and Moya

Lift off

With the growth of the number of libraries for handling HTTP stuff on iOS, there are a lot of common problems that stay unresolved. One of them is the refreshing of a session token.

Refresh session token of Auth0 with RxSwift and Moya

We use Auth0 in our current project. After a period of time, we have to give a signal to the HTTP-client to refresh session token. While Auth0 gives a method for refreshing a session token, Moya doesn’t provide any solution out of the box. It offers you either use multiple libraries like Heimdallr.swift or write our custom solution.

I am going to cover my experience below in a simple code snippet.

Write a custom MoyaProvider

Let’s write our custom MoyaProvider. We merely override existing parameters. As you can see from the snippet below, I pass the PreferencesHelper class in the constructor method.

In my project, I use this class to retrieve and update access token when the time comes. Below is a typical implementation of custom MoyaProvider. Also, in the request method, I return a Single object by RxSwift. We will modify this method a bit later.

import Foundation
import Moya
import RxSwift

final class CustomMoyaProvider<Target> where Target: Moya.TargetType {
    
    private let provider: MoyaProvider<Target>
    private let preferencesHelper: PreferencesHelper
    
    init(preferencesHelper: PreferencesHelper,
         endpointClosure: @escaping MoyaProvider<Target>.EndpointClosure = MoyaProvider.defaultEndpointMapping,
         requestClosure: @escaping MoyaProvider<Target>.RequestClosure = MoyaProvider.defaultRequestMapping,
         stubClosure: @escaping MoyaProvider<Target>.StubClosure = MoyaProvider.neverStub,
         manager: Manager = MoyaProvider<Target>.defaultAlamofireManager(),
         plugins: [PluginType] = [],
         trackInflights: Bool = false) {
        
        self.preferencesHelper = preferencesHelper
        self.provider = MoyaProvider(endpointClosure: endpointClosure,
                                     requestClosure: requestClosure,
                                     stubClosure: stubClosure,
                                     manager: manager,
                                     plugins: plugins,
                                     trackInflights: trackInflights)
    }

    func request(_ token: Target) -> Single<Moya.Response> {
        let response = provider.rx.request(token)
        return response
    }
    
}

Wrap methods for renewing a token with RxSwift

Next stop, in the class we need to add a method refreshSessionToken for refreshing a session token with RxSwift and the method renew provided by the Auth0 framework.

You can either put a client id and a domain in a property list file or pass these values to the authentication method manually. Since our project uses RxSwift for handling almost everything regarding business logic I made a wrapper which is responsible for returning either an error or an updated Credentials object.

private func refreshSessionToken(oldCredentials: Credentials) -> Single<Auth0.Credentials> {
    return Single.create { subscriber in
        Auth0.authentication(clientId: "MY_CLIENT_ID", domain: "MY_DOMAIN")
            .renew(withRefreshToken: oldCredentials.refreshToken!, scope: oldCredentials.scope)
            .start { result in
                switch result {
                case .success(let credentials):
                    subscriber(.success(credentials))
                case .failure(let error):
                    subscriber(.error(error))
                }
            }
        return Disposables.create()
    }
}

Override methods and catch errors

The next step is overriding the request method I shown in the very beginning. Here we only check either the status code is 401 which means that we need to refresh our session token or we have a different kind of error which we need to handle further. Actually, we return and then repeat our previously failed request to the server and finally return a correct response. 

At the end of the method, I call filterSuccessfulStatusCodes to handle an error as a common RxSwift action. Also, I pass updated values from the Credentials object to my PreferencesHelper class.

func request(_ token: Target) -> Single<Moya.Response> {
    let request = provider.rx.request(token)
    return request
        .flatMap { response in
            if response.statusCode == 401 {
                let oldCredentials = self.preferencesHelper.credentials!
                return self.refreshSessionToken(oldCredentials: oldCredentials)
                    .do(onNext: { self.preferencesHelper.credentials = Credentials(accessToken: $0.accessToken,
                                                                                   tokenType: $0.tokenType,
                                                                                   idToken: $0.idToken,
                                                                                   refreshToken: oldCredentials.refreshToken,
                                                                                   expiresIn: $0.expiresIn,
                                                                                   scope: oldCredentials.scope) })
                    .flatMap { _ in return self.request(token) }
            } else {
                return Single.just(response)
            }
        }
        .filterSuccessfulStatusCodes()
}

Now you can refresh token with RxSwift and Moya

 Now we have a completed solution for refreshing session token when a user opens the app after a long period when the token was expired. We wrote it using pure RxSwift approach and we return a classic error in case of fail. The main advantage of the solution is that it can be easily applicable for different services similar to Auth0 allowing to authenticate users in mobile apps.

Dmitry Chyrta

Dmitry Chyrta

Mobile app developer at datarockets

From our blog

Stay up to date

Check out our newsletter

© 2024 Red Panda Technology (dba datarockets). All Rights Reserved.