How to Make Apollo Client iOS Accept Self-Signed Certificates

1k views Asked by At

How does one make ApolloClient accept self-signed certificates? We are using it for internal apps, and calls to the GraphQL endpoints are failing because of the certification issues. I found this thread in the repository issues, but it seems that the API has changed some since that discussion, and the correct current approach is eluding me.

Currently, my integration test drops to the .failure branch reporting certificate validation issues:

        let apollo = AppConstants.current.apollo
        apollo.fetch( query: DriverStatementsIncompleteQuery( onepass: "test-user" ) ) { result in
            switch result {
            case .success( let results ):
                print( results )
                XCTAssertNil( results.errors )
                self.completedExpectation.fulfill()
            case .failure( let error ):
                XCTFail( error.localizedDescription )
                self.completedExpectation.fulfill()
            }
        }
        wait( for: [self.completedExpectation], timeout: 5 )
1

There are 1 answers

0
Sean McMains On BEST ANSWER

Ellen Shapiro on the Apollo iOS team very kindly pointed me in the right direction. Here's what I ended up with:

public struct PromiscuousApolloClientFactory {
    /// Creates an `ApolloClient` instance that is configured to work with certificates that the
    /// OS would otherwise deem invalid, like those that are self-signed.
    /// - Parameter endpointURL: The URL of the GraphQL endpoint.
    /// - Returns: The configured `ApolloClient` instance.
    public static func make( with endpointURL: URL ) -> ApolloClient {
        let store = ApolloStore( cache: InMemoryNormalizedCache() )
        let sessionConfig = URLSessionConfiguration.default
        let client = PromiscuousURLSessionClient( sessionConfiguration: sessionConfig )
        let provider = LegacyInterceptorProvider( client: client, store: store )
        let transport = RequestChainNetworkTransport( interceptorProvider: provider, endpointURL: endpointURL )
        
        return ApolloClient( networkTransport: transport, store: store )
    }
}

private class PromiscuousURLSessionClient: URLSessionClient {
    override func urlSession( _ session: URLSession,
                              didReceive challenge: URLAuthenticationChallenge,
                              completionHandler: @escaping ( URLSession.AuthChallengeDisposition, URLCredential? ) -> Void ) {
        let protectionSpace = challenge.protectionSpace
        
        guard protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
              let serverTrust = protectionSpace.serverTrust else {
                completionHandler( .performDefaultHandling, nil )
                return
        }
        
        let credential = URLCredential( trust: serverTrust )
        completionHandler( .useCredential, credential )
    }
}

NOTE BENE: This is generally a bad practice, as it short-circuits security protections that are there for your own good. If you have a path to use certificates that the OS can validate, do that instead. :)