Estou criando um aplicativo de bate-papo em Swift usando URLSession
. Tenho uma solicitação GET autenticada para buscar a lista de amigos do usuário atual. A solicitação funciona perfeitamente no Postman usando o mesmo token e URL, mas meu aplicativo iOS retorna consistentemente um erro 403 com { "detail": "Not Authenticated" }
.
O que eu tentei :
- Garantiu que o cabeçalho de autorização está incluído (
Bearer <token>
) - Registrei a URL completa e o token — eles estão corretos!
- Verifiquei se o servidor de backend está acessível (consigo fazer POST com sucesso)
- Confirmado que a mesma solicitação GET com token funciona bem no Postman
- HTTP inseguro permitido em
Info.plist
(servidor éhttp://52.23.164.179:8000
)
Meu código :
ContactListVC.swift :
private func fetchContacts() {
ContactService.shared.getFriends { [weak self] success, contacts in
guard let self = self else { return }
DispatchQueue.main.async {
if success, let contacts = contacts, !contacts.isEmpty {
self.contacts = contacts
self.tableView.reloadData()
} else {
self.contacts = []
self.showEmptyStateIfNeeded()
}
}
}
}
ContactService.swift :
func getFriends(completion: @escaping (Bool, [ChatPartner]?) -> Void) {
guard let request = contactRequest.getFriends() else {
completion(false, nil)
return
}
NetworkService.shared.sendRequest(request, parse: { data in
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let dataObject = json["data"] as? [String: Any],
let friendsArray = dataObject["friends"] as? [[String: Any]] else {
return nil
}
return friendsArray.compactMap { ChatPartner(json: $0) }
}) { result in
switch result {
case .success(let partners):
completion(true, partners)
case .failure:
completion(false, nil)
}
}
}
ContactRequest.swift :
func getFriends() -> URLRequest? {
guard let baseURL = BASE_URL else { return nil }
let url = baseURL.appendingPathComponent("friends")
var request = URLRequest(url: url)
request.setValue("Bearer \(token ?? "")", forHTTPHeaderField: "Authorization")
request.httpMethod = "GET"
return request
}
Serviço de rede.swift :
func sendRequest<T>(_ request: URLRequest, parse: @escaping (Data) -> T?, completion: @escaping (Result<T, NetworkError>) -> Void) {
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let httpResponse = response as? HTTPURLResponse, let data = data else {
completion(.failure(.invalidResponse))
return
}
switch httpResponse.statusCode {
case 200:
if let result = parse(data) {
completion(.success(result))
} else {
completion(.failure(.parsingFailed))
}
default:
completion(.failure(.statusCode(httpResponse.statusCode)))
}
}
task.resume()
}
Info.plist :
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>52.23.164.179</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
Questões :
- Por que estou recebendo uma resposta 403 no iOS, mas não no Postman com o mesmo token?
- É possível que alguns cabeçalhos ausentes (por exemplo, User-Agent) façam com que a API o rejeite?
Observações extras :
- Confirmei que a solicitação inclui o
Authorization
cabeçalho registrando-o. - Estou usando um endereço IP local (não HTTPS).
- O token não expirou (verificado no Postman).
- Aqui estão algumas imagens quando depuro:
Ao usar o Instruments.app, descobri que o código de status de resposta à solicitação é, na verdade, 307 - um redirecionamento.
URLSession
segue o redirecionamento enviando uma nova solicitação, sem o cabeçalho de autorização , e então você obtém um 403 no final.Por outro lado, o Postman mantém o cabeçalho, desde que o destino tenha o mesmo nome de host. Há também uma configuração para manter o cabeçalho mesmo que tenha um nome de host diferente.
O redirecionamento é de
/friends
para/friends/
, então imagino que alterar o caminho para/friends/
impediria o redirecionamento.Caso contrário, você pode passar um
URLSessionTaskDelegate
ao chamardataTask(...)
. O delegado passaria oAuthorization
cabeçalho para a nova solicitação. Aqui está um exemplo:Pode ser o motivo para não adicionar application/json accept e content-type, então o servidor pode rejeitar porque não há um cabeçalho padrão sensato do tipo
Você pode tentar adicionar isso ao cliente iOS
já está lá nos logs, mas eu recomendaria também testar esta solução adicionando isso também