Authentication with signed requests in Alamofire

With almost 30.000 stars on Github, you can tell that Alamofire is a popular framework to use for iOS and Mac projects. It makes network implementations easy to do and allows us to sign requests using a RequestAdapter for authentication.

Signing requests for authentication

API’s often require you to sign requests using for example JSON Web Tokens in combination with an Authorization header. Each outgoing request needs to have that authentication header set in order to be accepted by the backend.

RequestAdapter in Alamofire

Alamofire comes with two protocols, the RequestAdapter and RequestRetrier.

RequestAdapter:

A type that can inspect and optionally adapt a URLRequest in some manner if necessary.

RequestRetrier:

A type that determines whether a request should be retried after being executed by the specified session manager and encountering an error.

A combination of both can be a perfect way to implement your authentication system. If a request fails for authentication reasons, you can use the RequestRetrier to refresh the authentication token and trigger a retry. With the RequestAdapter you’re able to set the authentication header for each outgoing request.

Creating a request adapter

First, you need to create your own request adapter class.

final class JWTAccessTokenAdapter: RequestAdapter {
    typealias JWT = String
    private let accessToken: JWT

    init(accessToken: JWT) {
        self.accessToken = accessToken
    }

    func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
        var urlRequest = urlRequest

        if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix("https://api.authenticated.com") {
            /// Set the Authorization header value using the access token.
            urlRequest.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization")
        }

        return urlRequest
    }
}

Which you can use easily with your own SessionManager in Alamofire.

let sessionManager = SessionManager()
sessionManager.adapter = JWTAccessTokenAdapter(accessToken: "1234")

sessionManager.request("https://api.authenticated.com/get")

This is everything you need to authenticate your outgoing requests.

Creating a request retrier

The RequestRetrier protocol works quite similar. We extend our JWTAccessTokenAdapter and require it to refresh the token whenever we get a 401 Authorization Required response status code.

extension JWTAccessTokenAdapter: RequestRetrier {
    func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {

        guard let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 else {
            completion(false, 0.0)
            return
        }

        refreshToken { [weak self] accessToken in
            guard let strongSelf = self else { return }

            strongSelf.accessToken = accessToken
            completion(true, 0.0)
        }
    }
}

We trigger the completion callback after we’ve refreshed the access token to retry the failed request. The request will be triggered again and succeed with the refreshed access token.

Other usage examples of the request adapter

The request adapter is suitable for other use cases as well. Apart from using URLProtocols to print each outgoing request you can perfectly use the request adapter for this as well.

final class PrintRequestsAdapter: RequestAdapter {
    func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
        print("🚀 Running request: \(urlRequest.httpMethod ?? "") - \(urlRequest.url?.absoluteString ?? "")")

        return urlRequest
    }
}

The differences between a request adapter and a custom URLProtocol is that you will not get a callback when it’s completed. If you want to read more about this, check out Printing data requests using a custom URLProtocol.


Also published on Medium.