I am working on an iOS mobile application where I need to upload videos using the Youtube Data API. In general, I understand the workflow needed:
1) Send a request to an authentication endpoint using clientId, clientSecret, and other details.
2) The service authenticates the client, and sends back a request to a client-specified callbackURL containing the access token.
3) The client provides the token in the header whenever he/she wants sends future requests.
I've successfully uploaded Youtube videos using node.js script, but I'm having a lot of trouble understanding how to implement this in Swift. In my VideoManagementController, I have a button that triggers the upload of a sample.mp4 file:
let headers = ["Authorization": "Bearer \(self.newToken))"]
let path = Bundle.main.path(forResource: "sample", ofType: ".mp4")
let videodata : NSData = NSData.dataWithContentsOfMappedFile(path!)! as! NSData
print("TOKEN: \(String(describing: token))")
Alamofire.request("https://www.googleapis.com/upload/youtube/v3/videos?part=id", method: .post, parameters: nil, encoding: JSONEncoding.default, headers: headers).responseJSON { (response) in
print(response)
}
I am attempting to retrieve my access token in the viewDidLoad() stage of the controller:
let authorizationEndpoint = URL(string: "https://accounts.google.com/o/oauth2/v2/auth")
let tokenEndpoint = URL(string: "https://www.googleapis.com/oauth2/v4/token")
let configuration = OIDServiceConfiguration(authorizationEndpoint: authorizationEndpoint!, tokenEndpoint: tokenEndpoint!)
let request = OIDAuthorizationRequest(configuration: configuration, clientId: self.kClientID, scopes: [OIDScopeOpenID, OIDScopeProfile], redirectURL: self.KRedirectURI!, responseType: OIDResponseTypeCode, additionalParameters: nil)
let appDelegate: AppDelegate? = (UIApplication.shared.delegate as? AppDelegate)
print("REACHED")
appDelegate?.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, presenting: self, callback: {(_ authState: OIDAuthState?, _ error: Error?) -> Void in
if (authState != nil) {
print("TOKEN: Got authorization tokens. Access token: \(String(describing: authState?.lastTokenResponse?.accessToken))")
self.newToken = authState?.lastTokenResponse?.accessToken
self.authState = authState
}
else {
print("TOKEN: Authorization error: \(String(describing: error?.localizedDescription))")
self.authState = nil
}
})
The issue is that my access token retrieval code essentially hangs and never completes. It reaches the print statement "REACHED" but never comes out of this following portion of code:
appDelegate?.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, presenting: self, callback: {(_ authState: OIDAuthState?, _ error: Error?) -> Void in
if (authState != nil) {
print("TOKEN: Got authorization tokens. Access token: \(String(describing: authState?.lastTokenResponse?.accessToken))")
self.newToken = authState?.lastTokenResponse?.accessToken
}
else {
print("TOKEN: Authorization error: \(String(describing: error?.localizedDescription))")
self.authState = nil
}
I do not either get a new token or get the authorization error print out.
I suspect it has something to do with my callbackURL. I don't think Swift knows where to "listen" to get my access token from. A callbackURL endpoint is relatively easy to set up server-side in node.js, for example, but how do I do this in Swift? Or is this even the real issue?
i have tired in my code like this way , you also need to check sometime encoding type URLEncoding.httpBody hope it may help , it helps me in using through oauth
let headers = [
"Content-Type": "application/x-www-form-urlencoded"
]
Alamofire.request("https://accounts.google.com/o/oauth2/revoke?token={\(token)}", method: .post, parameters: parameters, encoding: URLEncoding.httpBody, headers: headers).responseJSON { (response:DataResponse<Any>) in
Related
I'm using the AppAuth library to get an access token for the Gmail API. I've successfully been able to create an Auth Session, and use the retrieved token to later fetch the emails.
In my AppDelegate I have two variables:
var currentAuthorizationFlow: OIDExternalUserAgentSession?
var authState: OIDAuthState?
In my SignInViewController, I have the following code for performing the authorization flow:
#objc func startAuthFlow() {
Analytics.logEvent("auth_started", parameters: nil)
let authorizationEndpoint = URL(string: "https://accounts.google.com/o/oauth2/v2/auth")!
let tokenEndpoint = URL(string: "https://www.googleapis.com/oauth2/v4/token")!
let configuration = OIDServiceConfiguration(authorizationEndpoint: authorizationEndpoint,
tokenEndpoint: tokenEndpoint)
let kRedirectURI: String = "com.googleusercontent.apps.someNumber:/oauthredirect";
guard let redirectURI = URL(string: kRedirectURI) else {
return
}
let appDelegate = UIApplication.shared.delegate as! AppDelegate
// builds authentication request
let request = OIDAuthorizationRequest(configuration: configuration,
clientId: "myID",
clientSecret: nil,
scopes: ["https://mail.google.com/"],
redirectURL: redirectURI,
responseType: OIDResponseTypeCode,
additionalParameters: nil)
// performs authentication request
print("Initiating authorization request with scope: \(request.scope ?? "nil")")
appDelegate.currentAuthorizationFlow =
OIDAuthState.authState(byPresenting: request, presenting: self) { authState, error in
if let authState = authState {
print("Got authorization tokens. Access token: " +
"\(authState.lastTokenResponse?.accessToken ?? "nil")")
A0SimpleKeychain().setString((authState.lastTokenResponse?.accessToken)!, forKey: "auth0-user-jwt")
A0SimpleKeychain().setString((authState.lastTokenResponse?.refreshToken)!, forKey: "auth0-user-jwt-refresh")
EmailFetcher.shared.setupEmailSession(token: (authState.lastTokenResponse?.accessToken)!)
} else {
print("Authorization error: \(error?.localizedDescription ?? "Unknown error")")
}
}
}
I then successfully save the token and the refresh token.
I saw that there was a tokenRefreshRequest() method for OIDAuthState, but my understanding is that you would need to pass in the refresh token to get a new, fresh token, correct? What's the missing piece to implementing this with AppAuth?
To refresh an access token you use a 'refresh token grant' message with arguments similar to this:
let request = OIDTokenRequest(
configuration: self.metadata!,
grantType: OIDGrantTypeRefreshToken,
authorizationCode: nil,
redirectURL: nil,
clientID: self.configuration.clientId,
clientSecret: nil,
scope: nil,
refreshToken: tokenData!.refreshToken!,
codeVerifier: nil,
additionalParameters: nil)
OIDAuthorizationService.perform(request) { tokenResponse, error in ...
Note that I'm not using the AuthState class in my sample, since I wanted to store tokens encrypted in the iOS keychain, so your code may be a little different. For something to compare against, you can run the code sample from by blog:
Code Sample
Blog Post
Create an authState from auth token or directly save authstate model into key chain then access latest refresh token using below methods.
authState
authState.performAction { (accessToken, authToken, error) in
guard let err = error else {return}
print("updated refresh token is : \(accessToken)")
}
I am trying to integrate braintree payment method in my swift code, but I am stuck in here, it is breaking with an error
{"error":{"statusCode":401,"name":"Error","message":"Authorization Required","code":"AUTHORIZATION_REQUIRED"}}
I have doing exactly the same as mentioned in braintree documentation. I don't know which authorization it is demanding, I do have a authorization token assigned to user when he/she logins, I am wondering if it is demanding that authorization token, but there is no such parameter in this code where I should place that token to generate client's token for payment method. Here the print statement when exeuted gives me this in log, "client Token is :
{"error":{"statusCode":401,"name":"Error","message":"Authorization Required","code":"AUTHORIZATION_REQUIRED"}}", I am bit confused in its calling also. I have just started these thing so I am very sorry I have done any obvious mistake. Thanks.
// TODO: Switch this URL to your own authenticated API
let clientTokenURL = NSURL(string: "https://braintree-sample-
merchant.herokuapp.com/client_token")!
let clientTokenRequest = NSMutableURLRequest(url:
clientTokenURL as URL)
clientTokenRequest.setValue("text/plain", forHTTPHeaderField:
"Accept")
URLSession.shared.dataTask(with: clientTokenRequest as
URLRequest) { (data, response, error) -> Void in
// TODO: Handle errors
if let error = error {
print("Error: \(error.localizedDescription)")
} else {
print("in Session")
let clientToken = String(data: data!, encoding:
String.Encoding.utf8)!
print("Client Token is : \(clientToken)")
}
}.resume()
}
One have to give authorization token in headers to avoid this error. Rather than that, this version of code will work fine.
completionHandler:#escaping (_ response: NSDictionary?, _ error: Error?) -
> ()) {
var headers: HTTPHeaders
// pass the authToken when you get when user login
let authToken = getAuthorizationToken()
if(self.isValidString(object: authToken as AnyObject)) {
headers = ["Authorization": authToken,
"Content-Type": "application/json",
"Accept": "application/json"]
} else {
headers = ["Content-Type": "application/json"]
}
AF.request(apiURL, method: .get, parameters: params as? Parameters,
encoding: JSONEncoding.default, headers: headers).validate().responseJSON
{
response in
self.handleResposne(response: response) { (response, error) in
completionHandler(response, error)
}
}
}
Currently learning how to add OAuth2 via Alamofire and getting confused. I'm using the password grant type and when a request fails I understand that the retrier kicks in and requests a token refresh. The confusing part is which one do I use?
Alamofire 4 using p2/OAuth2
Alamofire RequestRetrier + Request Adapter
The first one uses less code so unsure if its all the functionality I need. I also can't find a concrete example explaining this process.
I believe the following performs the refresh request?
private func refreshTokens(completion: RefreshCompletion) {
guard !isRefreshing else { return }
isRefreshing = true
let urlString = "\(baseURLString)/oauth2/token"
let parameters: [String: Any] = [
"access_token": accessToken,
"refresh_token": refreshToken,
"client_id": clientID,
"grant_type": "refresh_token"
]
sessionManager.request(urlString, withMethod: .post, parameters: parameters, encoding: .json).responseJSON { [weak self] response in
guard let strongSelf = self else { return }
if let json = response.result.value as? [String: String] {
completion(true, json["access_token"], json["refresh_token"])
} else {
completion(false, nil, nil)
}
strongSelf.isRefreshing = false
}
}
This is then passed back to adapt the previous request?
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: #escaping RequestRetryCompletion) {}
Is this the correct way of implementing this?
Thanks
I built a AppAuth test app for android using an Azure AD tenant and it works ok. Now I am trying to the same with iOS (Swift 4) and failing when trying to exchange an access code for access token. No error is returned, I do get an idToken but no accessToken or refreshToken. No other errors. Not sure what is going on. Without an access token I can't query the graph. I am using Azure AD v2. Here are some pieces of my code:
func appAuthAuthorize(authConfig: AuthConfig) {
let serviceConfiguration = OIDServiceConfiguration(
authorizationEndpoint: NSURL(string: authConfig.authEndPoint)! as URL,
tokenEndpoint: NSURL(string: authConfig.tokenEndPoint)! as URL)
let request = OIDAuthorizationRequest(configuration: serviceConfiguration, clientId: authConfig.clientId, scopes: [OIDScopeOpenID, OIDScopeProfile], redirectURL: NSURL(string: authConfig.redirectUri)! as URL, responseType: OIDResponseTypeCode, additionalParameters: nil)
doAppAuthAuthorization(authRequest: request)
}
func doAppAuthAuthorization(authRequest: OIDAuthorizationRequest) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.currentAuthorizationFlow = OIDAuthorizationService.present(authRequest, presenting: self, callback: {
(authorizationResponse, error) in
if (authorizationResponse != nil) {
self.authState = OIDAuthState(authorizationResponse: authorizationResponse!)
self.logMessage(message: "Got authorization code: \(String(describing: self.authState?.lastAuthorizationResponse.authorizationCode))")
self.doTokenRequest()
} else {
self.authState = nil
self.logMessage(message: "Authorization error: \(String(describing: error?.localizedDescription))")
}
})
}
func doTokenRequest() {
let tokenExchangeRequest = authState?.lastAuthorizationResponse.tokenExchangeRequest()
OIDAuthorizationService.perform(tokenExchangeRequest!) {
tokenResponse, error in
if tokenResponse == nil{
self.logMessage(message: "Token exchange error: \(error!.localizedDescription)")
} else {
self.authState?.update(with: tokenResponse!, error: error)
self.saveState()
self.logMessage(message: "Received token response with accesToken: \(tokenResponse!.idToken!)")
self.logMessage(message: "Received token response with accesToken: \(tokenResponse!.refreshToken!)")
self.logMessage(message: "Received token response with accesToken: \(tokenResponse!.accessToken!)")
self.retrieveUserProfile()
}
self.authState?.update(with: tokenResponse, error: error)
}
}
Got the answer. The problem is that depending on the authorization server, one has to use scopes defined for that server. In the code above, I used the default OpenId scopes of OIDScopeOpenID and OIDScopeProfile. As soon as I changed this to Azure AD scope of User.Read, everything started working correctly. So here is the net change to the code in function appAuthAuthorize:
func appAuthAuthorize(authConfig: AuthConfig) {
let serviceConfiguration = OIDServiceConfiguration(
authorizationEndpoint: NSURL(string: authConfig.authEndPoint)! as URL,
tokenEndpoint: NSURL(string: authConfig.tokenEndPoint)! as URL)
let request = OIDAuthorizationRequest(configuration: serviceConfiguration, clientId: authConfig.clientId, scopes: ["User.Read"], redirectURL: NSURL(string: authConfig.redirectUri)! as URL, responseType: OIDResponseTypeCode, additionalParameters: nil)
doAppAuthAuthorization(authRequest: request)
}
These are request headers:
let headers: HTTPHeaders = [
"Accept": "application/json",
"username": "someUserName",
"password": "aPasswordForSomeUserName"
]
When making a request with below code it's giving me "Garbage at the end". However, when I checked the response with JSON parser online. It's a valid JSON.
Alamofire.request("http://myserver/list.svc/random", headers: headers).responseJSON { response in
print(response)
}
I also tried making a request like this:
Alamofire.request("http://myserver/list.svc/random", headers: headers).responseString { response in
print(response)
}
I am getting this message in console: "401 UNAUTHORIZED".
What am I doing wrong? I believe, when using responseJSON completion block it's not complaining about Unauthorization, but it's complaining about bad JSON (or some garbage).
P.S. The same request works fine with Advance Rest Client (a chrome extension) and also in chrome browser.
I don't know how relevant this is to you anymore but I've got a working solution I'll post for any future reference.
So, I had two issues. The first one being that the Authorization header fell of the request when it was redirected. The second one being the NTLM-challenge from the server not being handled. The following code should be quite self explanatory I hope :) It asumes you store the username and password in variables name just that.
let credentialData = "\(username):\(password)".data(using: String.Encoding.utf8)!
let base64Credentials = credentialData.base64EncodedString(options: [])
request.addValue("Basic \(base64Credentials)", forHTTPHeaderField: "Authorization")
let manager = Alamofire.SessionManager.default
let delegate: Alamofire.SessionDelegate = manager.delegate
// This bit will re-add the auth headers for the redirected request
delegate.taskWillPerformHTTPRedirection = { session, task, response, request in
var redirectedRequest = request
if let originalRequest = task.originalRequest, let redirectheaders = originalRequest.allHTTPHeaderFields {
if let authorizationHeaderValue = redirectheaders["Authorization"] {
redirectedRequest.setValue(authorizationHeaderValue, forHTTPHeaderField: "Authorization")
}
if let contentTypeHeaderValue = redirectheaders["Content-Type"] {
redirectedRequest.setValue(contentTypeHeaderValue, forHTTPHeaderField: "Content-Type")
}
}
return redirectedRequest
}
// This bit looks at challenges received and applies the correct credentials
delegate.taskDidReceiveChallenge = { session, task, challenge in
var disposition: URLSession.AuthChallengeDisposition = .useCredential
var credential: URLCredential = URLCredential()
if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodNTLM) {
disposition = URLSession.AuthChallengeDisposition.useCredential
credential = URLCredential(user: username, password: password, persistence: URLCredential.Persistence.forSession)
}
if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
disposition = URLSession.AuthChallengeDisposition.useCredential
credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
}
return(disposition, credential)
}
manager.request(request).responseData { (response) in
// Handle response accordingly
}
Hope this helps someone.
In Swift4.2
Alamofire has built in NTLM auth support.
You can make a request like that
let user = "YOUR_USER_NAME OR EMAIL"
let password = "YOUR_PASSWORD"
let url = "YOUR_API_URL"
let credential = URLCredential(user: user, password: password, persistence: .forSession)
//These headers are optional based on your api and your server.
//There were required for me
let headers = ["Accept": "application/json;odata=verbose",
"Content-type": "application/json;odata=verbose"]
Alamofire.request(url, method: .get, headers: headers).authenticate(usingCredential: credential).responseJSON {
(response) in
switch response.result {
case .success:
if let value = response.result.value {
print("The server response is: ", value)
}else{
print("There is error in the server response")
}
case .failure (let error):
print("The NTLM request error is: ", error.localizedDescription)
}
}