Alamofire request with authorization bearer token and additional headers Swift - swift

My API includes authorization bearer token and three additional headers. My problem is I'm not sending the bearer token right (Postman return the correct data not my simulator). I see a lot of examples for using the request adapter but can I not use that? Thanks!
The auth is actually in the authorization tab not in the header.
**Updated:
Solved the problem by following the documentation.
HTTP Headers
Here is the Alamofire function with working codes:
func getBetsData(completion: ((Bool) -> ())? = nil) {
guard let token = defaults.string(forKey: "token") else {
return
}
let headers: HTTPHeaders = [
.authorization(bearerToken: token),
.init(name: "bet_type", value: type),
.init(name: "bet_status", value: status),
.init(name: "page", value: String(page))
]
AF.request("https://example.com", headers: headers).responseDecodable(of: Bets.self) { response in
switch response.result {
case .success:
if let data = response.data {
do {
let bets = try JSONDecoder().decode(Bets.self, from: data)
print("message: \(bets.message)")
self.setupTableData()
completion?(true)
} catch {
print("Error: \(error)")
completion?(false)
}
}
case.failure(let error):
print(error)
completion?(false)
}
}
}

You can add the headers directly:
let headers: HTTPHeaders = [.authorization(bearerToken: token)]
Additionally, if you're decoding a Decodable value from the response, you should not use responseJSON, as that decodes the Data using JSONSerialization and then you just parse it again. Instead, you should use responseDecodable.
AF.request(...).responseDecodable(of: Bets.self) { response in
// Use response.
}
That will be much more efficient and will capture errors for you automatically.

As mention by Dilan only token is not enought you will need to Bearer in the same Header parameter.
Here is one of the best method to handle Token Request and Retrier in all the request you send to server in the application
https://www.avanderlee.com/swift/authentication-alamofire-request-adapter/
By this you don't need to handle token manually in all the webservice.

Related

RestAPI response is not same as Postman Response

I am facing a very strange issue while calling RESTful API. I have a login API, I tested it in Postman and it's working perfectly fine. Here is a screenshot.
But once I call it using Alamofire, I get the response as "status :" 1 and "message" : 'Incorrect Credentials' It's the same email and password that I'm using in postman but still I get this response even though my email and password is correct. I have tested it on multiple email and passwords, and for every user it gives me same error.
Here is my login Function..
public func login(email: String, password: String, success: #escaping (UserData) -> (), failure: errorClosure)
{
let parameters: Parameters = [
"email": "\(email)",
"password": "\(password)"
]
session.request(Config.loginURL, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseJSON
{ response in
switch response.result
{
case .success(_):
let json = response.value
print("JSON: \(json ?? "errr")")
MappingHelper.ModelFromResponse(UserData.self, response: response, success: { (result) in
success(result)
}, failure: failure)
case .failure(let error):
failure?(error)
}
}
}
This is how i'm calling it..
helper.login(email: email, password: password) { (UserData) in
print(UserData)
} failure: { (error) in
print(error)
}
Debugging..
The reason I am using session.request instead of AF.request is because when I use AF.request it throws me a certificate error.
The certificate for this server is invalid. You might be connecting to a server that is pretending to be "DOMAIN NAME" which could put your confidential information at risk.
So to bypass this error I created a session with the help of some answer from SO.
private let session: Session = {
let manager = ServerTrustManager(evaluators: ["******.com": DisabledTrustEvaluator()])
let configuration = URLSessionConfiguration.af.default
return Session(configuration: configuration, serverTrustManager: manager)
}()
I think the error is JSONEncoding.default because you don't want to send a JSON body according to your Postman screenshot. You'll want to use a url-form-encoded as defined here

Basic Auth not working in Alamofire Swift

I am trying to get a Client Credential token, which is needed for Spotify's public Web API to search for public tracks.
It is extremely simple to get a token using postman.
REQUEST BODY PARAMETER: VALUE: grant_type Set it to client_credentials.
The header of this POST request must contain the following parameter:
HEADER PARAMETER: VALUE: Authorization Base 64 encoded string that contains the client ID and client secret key. The field must have the format: Authorization: Basic <base64 encoded client_id:client_secret>
I need to get a Spotify token in Swift. I started with this decodable struct:
struct SpotifyAuth: Decodable {
var access_token: String
var expires_in: Int
}
I have then tried dozens of variations of the following code to no avail:
let headers : HTTPHeaders = ["Content-Type":"application/x-www-form-urlencoded"]
let paramaters : Parameters = ["grant_type": "client_credentials", ]
AF.request("https://accounts.spotify.com/api/token", method: .post, parameters: paramaters, encoding: URLEncoding.httpBody, headers: headers ).authenticate(username: "{clientID}", password:"{clientSecrent}").responseJSON { response in
if response.error == nil {
do {
let spotifyAuth = try JSONDecoder().decode(SpotifyAuth.self, from: response.data!)
completion(.success(spotifyAuth))
} catch let error {
completion(.failure(error))
}
} else {
completion(.failure(response.error!))
}
}
Is anyone aware of what I am doing wrong/the correct way of acquiring a simple token from Spotify?
authenticate() is used to respond to authentication challenges. It does not unconditionally add the header. If the server does not respond with a Basic challenge you'll need to add the header yourself. Alamofire has a convenience method to help: HTTPHeader.authorization(username:password:). This will properly generate the Basic header for you.

How to handle waiting for a nested URLSession to complete

I have a function that provides a layer over URLSession. When this function is called I would like to check if the current access token as expired, if it has, I would to pause the current call, make a call to request a new token, replace the existing entry in the Keychain, then continue with the call.
func profile(with endpoint: ProfilesEndpoint, method: HTTPMethod, body: String?, headers: [String: String]?, useAuthToken: Bool = true, completion: #escaping (Either<ProfileResponse>) -> Void) {
var request = endpoint.request
request.httpMethod = method.rawValue
if let body = body {
request.httpBody = body.data(using: .utf8)
}
if useAuthToken {
if !AuthService.shared.isTokenValid {
let group = DispatchGroup()
group.enter()
OAuthService.shared.requestRefreshToken()
group.leave()
}
let (header, token) = AuthService.shared.createAuthHeaderForNetworkRequest()
request.addValue(token, forHTTPHeaderField: header)
}
if let headers = headers {
for (key, value) in headers {
request.addValue(value, forHTTPHeaderField: key)
}
}
execute(with: request, completion: completion)
}
A mechanism existing for handling the Keychain so please assume this is in place.
The function to request a new token looks like
func requestRefreshToken() -> Void {
if let refreshToken = KeychainWrapper.standard.string(forKey: "RefreshToken") {
var postBody = "grant_type=\(refreshTokenGrantType)&"
postBody += "client_id=\(clientId)&"
postBody += "refresh_token=\(refreshToken)&"
let additionalHeaders = [
"Content-Type": "application/x-www-form-urlencoded;"
]
APIClient.shared.identity(with: .token, method: .post, body: postBody, headers: additionalHeaders, useAuthToken: false) { either in
switch either {
case .success(let results):
guard let accessToken = results.accessToken, let refreshToken = results.refreshToken else { return }
AuthService.shared.addTokensToKeyChain(tokens: ["AccessToken": accessToken, "RefreshToken": refreshToken])
case .error(let error):
print("Error:", error)
}
}
}
}
I was expecting the executing to pause here
group.enter()
OAuthService.shared.requestRefreshToken()
group.leave()
However it does not.
How I can await this call to complete before completing the rest of the function?
Add to your requestRefreshToken method completion handler which will get executed when your request for token is completed
func requestRefreshToken(_ completion: #escaping () -> Void) {
if let refreshToken = KeychainWrapper.standard.string(forKey: "RefreshToken") {
var postBody = "grant_type=\(refreshTokenGrantType)&"
postBody += "client_id=\(clientId)&"
postBody += "refresh_token=\(refreshToken)&"
let additionalHeaders = [
"Content-Type": "application/x-www-form-urlencoded;"
]
APIClient.shared.identity(with: .token, method: .post, body: postBody, headers: additionalHeaders, useAuthToken: false) { either in
switch either {
case .success(let results):
guard let accessToken = results.accessToken, let refreshToken = results.refreshToken else {
completion()
return
}
AuthService.shared.addTokensToKeyChain(tokens: ["AccessToken": accessToken, "RefreshToken": refreshToken])
case .error(let error):
print("Error:", error)
}
completion()
}
}
}
then leave dispatchGroup in closure and also add group.wait() (after calling request method) for pausing current thread until group's task has completed
group.enter()
OAuthService.shared.requestRefreshToken {
group.leave()
}
group.wait()
Note: you can add boolean parameter to completion to check if request for token was successful or not
Why not do this underneath instead of on top? This seems like an ideal use for NSURLProtocol. Basically:
The URL protocol snags creation of the request in its init method and saves off all the provided parameters.
The URL protocol allocates a private NSURLSession instance and stores it in a property. That session should not be configured to use the protocol, or else you'll get an infinite loop.
When the protocol gets a startLoading() call, it checks the validity of the token, then:
Fires off a request for a new token if needed, in that private session.
Upon response, or if the token is still valid, fires off the real request — again, in that private session.
With that approach, the entire authentication process becomes basically transparent to the app except for the need to add the protocol into protocolClasses on the session configuration when creating a new session.
(There are a number of websites, including developer.apple.com, that provide examples of custom NSURLProtocol subclasses; if you decide to go with this approach, you should probably use one of those sample code projects as a starting point.)
Alternatively, if you want to stick with the layer-on-top approach, you need to stop thinking about "stopping" the method execution and start thinking about it as "doing the last part of the method later". It's all about asynchronous thinking.
Basically:
Factor out the last part of the method (the code that performs the actual request) into a new method (or a block).
If the token is valid, call that method immediately.
If the token is invalid, asynchronously fetch the new token.
In the token fetch call's completion callback, call the method to perform the actual request.

Alamofire POST encoding issue

I'm trying to login to a web service as follows:
func Login(completionHandler:#escaping (Bool) -> ()) {
let url = MyUrl
let parameters: Parameters = [
"Password":password,
"StayLoggedIn":NSNumber(value: true),
"UserName":username
]
var headers:HTTPHeaders = commonHeaders()
headers["Content-Type"] = "application/json;charset=UTF-8"
Alamofire.request(url, method:.post, parameters:parameters, headers:headers).responseJSON { response in
switch response.result {
case .success:
debugPrint(response)
case .failure(let error):
print(error)
}
}
}
Somehow, the server returns a 501 error.
I'm in the process of rewriting an Obj-c app in Swift, and replacing NSURLSessions with Alamofire into the bargain.
What I see in my working obj-c app, is that the headers sent to the server contain
Content-Type: application/json;charset=UTF-8
As you can see, I explicitly add this header to my request, but somehow, it is not sent to the server.
What I also see, is that my login credentials are not sent to the server.
So, I guess my question is: how do I tell Alamofire to use the correct encoding/content type?
try this encoding application/x-www-form-urlencoded instead of application/json;charset=UTF-8

AlamoFire Authorization header Oauth

I'm trying to get tweets from a specific user using Alamofire and the twitter API. I have successfully made the call with a cURL command, but I can't make it with AlamoFire. It gives a 215 error for Bad Authentication data.
curl --get 'https://api.twitter.com/1.1/statuses/user_timeline.json' --data 'count=2&screen_name=twitterapi' --header 'Authorization: OAuth oauth_consumer_key="lfa3XmdguuAqu3MoEQ6Fo01s0", oauth_nonce="0d50d7938a68be1ed73bcf02d4cc57e8", oauth_signature="8wZF1F%2B70okWLOi4s7dwKlJKc5w%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1460320508", oauth_version="1.0"' --verbose
let key = "lfa3XmdguuAqu3MoEQ6Fo01s0"
let secret = "25EUfqUstgdjZFgYnecrYuPkG6fDkruCjRXr905kM7475W2G63"
let data = ["screen_name": "twitterapi", "count": 2]
let path = "https://api.twitter.com/1.1/statuses/user_timeline.json"
Alamofire.request(.GET, path, parameters: data)
.authenticate(user: key, password: secret)
.responseJSON { response in
if let JSON = response.result.value {
print("JSON: \(JSON)")
}
}
How do I give the custom header Authorization in AlamoFire? I think I need an auth token, but I'm not sure where to start getting those. thanks
To give a custom Authorization, you have to do this:
let headers = ["Authorization": "Bearer \(Token.sharedInstance.value!)"]
Alamofire.request(.PUT, url, headers: headers, parameters: data, encoding: .JSON).responseJSON { response in
}
Token Class:
class Token {
var value:String?
static let sharedInstance = Token()
private init() { }
}