I have a Rails API using Devise JWT for authentication.
When trying to login using Postman it works like a charm:
But when I call from my Swift app, I have a 401 Unauthorized with invalid credentials.
Here is the data I get from printing in my Swift console :
data: {"status":{"code":200,"message":"Logged in successfully.","data":{"user":{"id":null,"email":"","firstname":null,"lastname":null}}}}
It seems like the login works but I have no data associated.
I have an AuthService and a LoginViewModel as follow :
// AuthService
import Foundation
enum AuthenticationError: Error {
case invalidCredentials
case custom(errorMessage: String)
}
enum NetworkError: Error {
case invalidURL
case noData
case decodingError
}
struct LoginRequestBody: Codable {
let email: String
let password: String
}
struct LoginResponse: Codable {
let token: String?
let message: String?
let success: Bool?
}
class AuthService {
func getAllUsers(token: String, completion: @escaping (Result<[UserV2], NetworkError>) -> Void) {
guard let url = URL(string: "http://localhost:3001/api/users/index") else {
completion(.failure(.invalidURL))
return
}
var request = URLRequest(url: url)
request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data, error == nil else {
completion(.failure(.noData))
return
}
guard let accounts = try? JSONDecoder().decode([UserV2].self, from: data) else {
completion(.failure(.decodingError))
return
}
completion(.success(accounts))
}.resume()
}
func login(email: String, password: String, completion: @escaping (Result<String, AuthenticationError>) -> Void) {
guard let url = URL(string: "http://localhost:3001/login") else {
completion(.failure(.custom(errorMessage: "URL is not correct")))
return
}
let body = LoginRequestBody(email: email, password: password)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try? JSONEncoder().encode(body)
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data, error == nil else {
completion(.failure(.custom(errorMessage: "No data")))
return
}
try! JSONDecoder().decode(LoginResponse.self, from: data)
print("data: \(String(decoding: data, as: UTF8.self))")
guard let loginResponse = try? JSONDecoder().decode(LoginResponse.self, from: data) else {
completion(.failure(.invalidCredentials))
return
}
guard let token = loginResponse.token else {
completion(.failure(.invalidCredentials))
return
}
completion(.success(token))
}.resume()
}
}
// LoginViewModel
import Foundation
class LoginViewModel: ObservableObject {
var email: String = ""
var password: String = ""
@Published var isAuthenticated: Bool = false
protocol AuthenticationFormProtocol {
var formIsValid: Bool { get }
}
func login() {
let defaults = UserDefaults.standard
AuthService().login(email: email, password: password) { result in
print("result: \(result)")
switch result {
case .success(let token):
defaults.setValue(token, forKey: "jsonwebtoken")
DispatchQueue.main.async {
self.isAuthenticated = true
}
case .failure(let error):
print(error.localizedDescription)
}
}
}
func signout() {
let defaults = UserDefaults.standard
defaults.removeObject(forKey: "jsonwebtoken")
DispatchQueue.main.async {
self.isAuthenticated = false
}
}
}
I am new to Swift so maybe I'm missing something?

"user"). Addprint(Sending in body: \(String(data: request.httpBody!), encoding: .utf8)")you'll seedata: {"user":{"email":"[email protected]","password":"test1234"}}but still get a 401 with invalid credentialsThe operation couldn’t be completed. (MyAppName.AuthenticationError error 1.)httpsconnection. To usehttp, you need to set the "NSAppTransportSecurity" in your Info.plist to allowhttpconnection to the (local) server. Have you done that?