0

I have a Rails API using Devise JWT for authentication.

When trying to login using Postman it works like a charm:

Response in Postman

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?

15
  • 3
    Your body doesn't match the one in Postman.. You're missing a hierarchy step ("user"). Add print(Sending in body: \(String(data: request.httpBody!), encoding: .utf8)") you'll see Commented Sep 26, 2024 at 14:42
  • @Larme added a struct to conform to the data. Now i have this: data: {"user":{"email":"[email protected]","password":"test1234"}} but still get a 401 with invalid credentials Commented Sep 27, 2024 at 8:42
  • Print the request header fields, and check if it's the same as the one in Postman. Commented Sep 27, 2024 at 8:46
  • Yes they are the same. I don't know why but now I don't have any 401 in my rails server, only 200 OK. But in my swift console I still have The operation couldn’t be completed. (MyAppName.AuthenticationError error 1.) Commented Sep 27, 2024 at 9:06
  • Note Apple requires https connection. To use http, you need to set the "NSAppTransportSecurity" in your Info.plist to allow http connection to the (local) server. Have you done that? Commented Sep 27, 2024 at 9:10

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.