I am attempting to use OAuth 1 with the Twitter API using OAuthSwift. I have completed all of the setup steps while building successfully throughout, but on the last step I am getting an error. When I implement the following code, I get an error saying "The operations cannot be completed. (OAuthSwiftError error -10)". I am thinking it might have something to do with the callback URL but it is very unclear and there isn't much documentation on this error. Or maybe there is something wrong with my key or secret? I have copied them directly from the Twitter dev site.
let oauthswift = OAuth1Swift(
consumerKey: CONSUMER_KEY,
consumerSecret: CONSUMER_SECRET,
requestTokenUrl: "https://api.twitter.com/oauth/request_token",
authorizeUrl: "https://api.twitter.com/oauth/authorize",
accessTokenUrl: "https://api.twitter.com/oauth/access_token"
)
let _ = oauthswift.authorize(
withCallbackURL: URL(string: "oauth-swift://oauth-callback/twitter")!,
success: { credential, response, parameters in
print(credential.oauthToken)
print(credential.oauthTokenSecret)
print(parameters["user_id"])
// Do your request
},
failure: { error in
print(error.localizedDescription)
}
)
I needed to create class attributes. handle and oauthswift are declared as attributes of the class and now the code works. Revised code below:
var oauthswift: OAuth1Swift!
var handle: OAuthSwiftRequestHandle!
var newOAuthToken: String!
override func viewDidLoad() {
super.viewDidLoad()
print("BEFORE OAUTHSWIFT")
oauthswift = OAuth1Swift(
consumerKey: CONSUMER_KEY,
consumerSecret: CONSUMER_SECRET,
requestTokenUrl: "https://api.twitter.com/oauth/request_token",
authorizeUrl: "https://api.twitter.com/oauth/authorize",
accessTokenUrl: "https://api.twitter.com/oauth/access_token"
)
print("AFTER OAUTHSWIFT")
handle = oauthswift.authorize(
withCallbackURL: URL(string: "oauth-swift://oauth-callback/twitter")!,
success: { credential, response, parameters in
print("OAuthToken: \(credential.oauthToken)")
print("OAuthSecret: \(credential.oauthTokenSecret)")
print("User ID: \(parameters["user_id"]!)")
// Do your request
},
failure: { error in
print(error.localizedDescription)
print(self.handle)
}
)
// Do any additional setup after loading the view, typically from a nib.
}
This is not the answer to your question.
But try using the Fabric for install TwitterKit.
For me personally this is an easier way.
Related
I know this question is asked a lot, but I can't figure out how to apply any answers to my program. Sorry in advance this async stuff makes absolutely zero sense to me.
Basically, I have a button in SwiftUI that, when pressed, calls a function that makes two API calls to Google Sheets using Alamofire and GoogleSignIn.
Button("Search") {
if fullName != "" {
print(SheetsAPI.nameSearch(name: fullName, user: vm.getUser()) ?? "Error")
}
}
This function should return the values of some cells on success or nil on an error. However, it only ever prints out "Error". Here is the function code.
static func nameSearch<S: StringProtocol>(name: S, advisory: S = "", user: GIDGoogleUser?) -> [String]? {
let name = String(name)
let advisory = String(advisory)
let writeRange = "'App Control'!A2:C2"
let readRange = "'App Control'!A4:V4"
// This function can only ever run when user is logged in, ! should be fine?
let user = user!
let parameters: [String: Any] = [
"range": writeRange,
"values": [
[
name,
nil,
advisory
]
]
]
// What I want to be returned
var data: [String]?
// Google Identity said use this wrapper so that the OAuth tokens refresh
user.authentication.do { authentication, error in
guard error == nil else { return }
guard let authentication = authentication else { return }
// Get the access token to attach it to a REST or gRPC request.
let token = authentication.accessToken
let headers: HTTPHeaders = ["Authorization": "Bearer \(token)"]
AF.request("url", method: .put, parameters: parameters, encoding: JSONEncoding.default, headers: headers).responseString { response in
switch response.result {
case .success:
// I assume there is a better way to make two API calls...
AF.request("anotherURL", headers: headers).responseDecodable(of: NameResponseModel.self) { response2 in
switch response2.result {
case .success:
guard let responseData = response2.value else { return }
data = responseData.values[0]
// print(responseData.values[0]) works fine
case .failure:
print(response2.error ?? "Unknown error.")
data = nil
}
}
case .failure:
print(response.error ?? "Unknown error.")
data = nil
}
}
}
// Always returns nil, "Unknown error." never printed
return data
}
The model struct for my second AF request:
struct NameResponseModel: Decodable { let values: [[String]] }
An example API response for the second AF request:
{
"range": "'App Control'!A4:V4",
"majorDimension": "ROWS",
"values": [
[
"Bob Jones",
"A1234",
"Cathy Jones",
"1234 N. Street St. City, State 12345"
]
]
}
I saw stuff about your own callback function as a function parameter (or something along those lines) to handle this, but I was completely lost. I also looked at Swift async/await, but I don't know how that works with callback functions. Xcode had the option to refactor user.authentication.do { authentication, error in to let authentication = try await user.authentication.do(), but it threw a missing parameter error (the closure it previously had).
EDIT: user.authentication.do also returns void--another reason the refactor didn't work (I think).
There is probably a much more elegant way to do all of this so excuse the possibly atrocious way I did it.
Here is the link to Google Identity Wrapper info.
Thanks in advance for your help.
Solved my own problem.
It appears (according to Apple's async/await intro video) that when you have an unsupported callback that you need to run asynchronously, you wrap it in something called a Continuation, which allows you to manually resume the function on the thread, whether throwing or returning.
So using that code allows you to run the Google Identity token refresh with async/await.
private static func auth(_ user: GIDGoogleUser) async throws -> GIDAuthentication? {
typealias AuthContinuation = CheckedContinuation<GIDAuthentication?, Error>
return try await withCheckedThrowingContinuation { (continuation: AuthContinuation) in
user.authentication.do { authentication, error in
if let error = error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: authentication)
}
}
}
}
static func search(user: GIDGoogleUser) async throws {
// some code
guard let authentication = try await auth(user) else { ... }
// some code
}
I then ran that before using Alamofire's built-in async/await functionality for each request (here's one).
let dataTask = AF.request(...).serializingDecodable(NameResponseModel.self)
let response = try await dataTask.value
return response.values[0]
So for the last 2 days I've been stumped on how to implement this Stripe API, it's by far been the hardest thing to wrap my head around with. So I decided to integrate the Stripe functionality using Firebase and Cloud Functions and I've been seeing that it's server-less which is great.
I've been trying to follow this article on iOS Stripe API integration and this article showing how to create the cloud functions involving Stripe and I so far have been able to create a Stripe customer upon new user creation. After that, I'm pretty much lost on how to do what I want to do next, which is create ephemeral keys.
I have this function I snagged from another SO post:
exports.createEphemeralKeys = functions.https.onRequest((req, res) => {
var api_version = req.body.api_version;
var customerId = req.body.customerId;
if (!api_version) {
res.status(400).end();
return;
}
stripe.ephemeralKeys.create(
{ customer: customerId },
{ stripe_version: api_version },
function(err, key) {
return res.send(key);
});
});
And this is the method in the MyAPIClient.swift file:
func createCustomerKey(withAPIVersion apiVersion: String, completion: #escaping STPJSONResponseCompletionBlock) {
let url = self.baseURL.appendingPathComponent("ephemeral_keys")
var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false)!
urlComponents.queryItems = [URLQueryItem(name: "api_version", value: apiVersion)]
var request = URLRequest(url: urlComponents.url!)
request.httpMethod = "POST"
let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
guard let response = response as? HTTPURLResponse,
response.statusCode == 200,
let data = data,
let json = ((try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any]) as [String : Any]??) else {
completion(nil, error)
return
}
completion(json, nil)
})
task.resume()
}
Now this is where I get confused, since integrating Stripe with Firebase is server-less, what are we supposed to input in the baseURL? Currently the baseURL variable is empty, but I'm also getting thrown errors that .appendingPathComponent is not available as well. Forgive me if this is a dumb question, but I'd much rather look like a complete idiot and eventually figure out how to successfully integrate this API, than not ask anything at all. Thanks in advance.
The baseURL should be set to the URL where your Firebase functions live. Have a look at the Firebase documentation for invoking an HTTP function for details.
The URL will be something like this:
https://<region>-<project-id>.cloudfunctions.net/
There are some similar question however none of those solve my problem.
Using Xcode 11.1
MacOS Cataline 10.15
I clone this "active-directory-b2c-ios-swift-native-msal" and try to run getting Error "Could not acquire token: Error Domain=MSALErrorDomain Code=-50000 "(null)" UserInfo={MSALErrorDescriptionKey=Failed to start an interactive session, MSALInternalErrorCodeKey=-42008, MSALCorrelationIDKey=C9207A45-6A7D-416B-90E4-93E08F28A637}"
After changing B2C details same issue getting .
Please let me know what is issue, Is this issue for Xcode/OS/MSAL version or some issue with Code??
I tried with default configuration mention in git repo "active-directory-b2c-ios-swift-native-msal" and also tried after below changed let kTenantName = "dovervsg.onmicrosoft.com" // Your tenant name
let kClientID = "xxxxxxxxxxxxxxxxxxxxxxx" // Your client ID from the portal when you created your application
let kSignupOrSigninPolicy = "B2C_1-policy" // Your signup and sign-in policy you created in the portal
let kEditProfilePolicy = "b2c_1_edit_profile" // Your edit policy you created in the portal
let kResetPasswordPolicy = "B2C_1_reset_password" // Your reset password policy you created in the portal
let kGraphURI = "https://dev-vsg.dovertech.co.in" // This is your backend API that you've configured to accept your app's tokens
let kScopes: [String] = ["https://dovervsg.onmicrosoft.com/User.Read"] // This is a scope that you've configured your backend API to look for.
// tried with this scope format as well, let kScopes: [String] = ["https://dovervsg.onmicrosoft.com/api/User.Read"] // This is a scope that you've configured your backend API to look for.
let kTenantName = "dovervsg.onmicrosoft.com" // Your tenant name
let kClientID = "xxxxxxxxxxxxxxxxxxxxxxx" // Your client ID from the portal when you created your application
let kSignupOrSigninPolicy = "B2C_1-policy" // Your signup and sign-in policy you created in the portal
let kEditProfilePolicy = "b2c_1_edit_profile" // Your edit policy you created in the portal
let kResetPasswordPolicy = "B2C_1_reset_password" // Your reset password policy you created in the portal
let kGraphURI = "https://dev-vsg.dovertech.co.in" // This is your backend API that you've configured to accept your app's tokens
let kScopes: [String] = ["https://dovervsg.onmicrosoft.com/User.Read"] // This is a scope that you've configured your backend API to look for.
// tried with this scope format as well, let kScopes: [String] = ["https://dovervsg.onmicrosoft.com/api/User.Read"] // This is a scope that you've configured your backend API to look for
// DO NOT CHANGE - This is the format of OIDC Token and Authorization endpoints for Azure AD B2C.
let kEndpoint = "https://login.microsoftonline.com/tfp/%#/%#"
var application: MSALPublicClientApplication!
var accessToken: String?
#IBOutlet weak var loggingText: UITextView!
#IBOutlet weak var signoutButton: UIButton!
#IBOutlet weak var callGraphApiButton: UIButton!
#IBOutlet weak var editProfileButton: UIButton!
#IBOutlet weak var refreshTokenButton: UIButton!
override func viewDidAppear(_ animated: Bool) {
//super.viewDidLoad()
do {
/**
Initialize a MSALPublicClientApplication with a MSALPublicClientApplicationConfig.
MSALPublicClientApplicationConfig can be initialized with client id, redirect uri and authority.
Redirect uri will be constucted automatically in the form of "msal<your-client-id-here>://auth" if not provided.
The scheme part, i.e. "msal<your-client-id-here>", needs to be registered in the info.plist of the project
*/
let authority = try self.getAuthority(forPolicy: self.kSignupOrSigninPolicy)
// Provide configuration for MSALPublicClientApplication
// MSAL will use default redirect uri when you provide nil
let pcaConfig = MSALPublicClientApplicationConfig(clientId: kClientID, redirectUri: nil, authority: authority)
self.application = try MSALPublicClientApplication(configuration: pcaConfig)
} catch {
self.updateLoggingText(text: "Unable to create application \(error)")
}
}
/**
This button will invoke the authorization flow and send the policy specified to the B2C server.
Here we are using the `kSignupOrSignInPolicy` to sign the user in to the app. We will store this
accessToken for subsequent calls.
*/
#IBAction func authorizationButton(_ sender: UIButton) {
do {
/**
authority is a URL indicating a directory that MSAL can use to obtain tokens. In Azure B2C
it is of the form `https://<instance/tfp/<tenant>/<policy>`, where `<instance>` is the
directory host (e.g. https://login.microsoftonline.com), `<tenant>` is a
identifier within the directory itself (e.g. a domain associated to the
tenant, such as contoso.onmicrosoft.com), and `<policy>` is the policy you wish to
use for the current user flow.
*/
let authority = try self.getAuthority(forPolicy: self.kSignupOrSigninPolicy)
/**
Acquire a token for a new account using interactive authentication
- scopes: Permissions you want included in the access token received
in the result in the completionBlock. Not all scopes are
gauranteed to be included in the access token returned.
- completionBlock: The completion block that will be called when the authentication
flow completes, or encounters an error.
*/
let webViewParameters = MSALWebviewParameters(parentViewController: self)
let parameters = MSALInteractiveTokenParameters(scopes: kScopes, webviewParameters: webViewParameters)
parameters.promptType = .selectAccount
print( parameters.promptType = .selectAccount)
parameters.authority = authority
debugPrint( parameters.authority = authority)
application.acquireToken(with: parameters) { (result, error) in
guard let result = result else {
self.updateLoggingText(text: "Could not acquire token: \(error ?? "No error informarion" as! Error)")
return
}
self.accessToken = result.accessToken
self.updateLoggingText(text: "Access token is \(self.accessToken ?? "Empty")")
self.signoutButton.isEnabled = true
self.callGraphApiButton.isEnabled = true
self.editProfileButton.isEnabled = true
self.refreshTokenButton.isEnabled = true
}
} catch {
self.updateLoggingText(text: "Unable to create authority \(error)")
}
}
#IBAction func editProfile(_ sender: UIButton) {
do {
/**
authority is a URL indicating a directory that MSAL can use to obtain tokens. In Azure B2C
it is of the form `https://<instance/tfp/<tenant>/<policy>`, where `<instance>` is the
directory host (e.g. https://login.microsoftonline.com), `<tenant>` is a
identifier within the directory itself (e.g. a domain associated to the
tenant, such as contoso.onmicrosoft.com), and `<policy>` is the policy you wish to
use for the current user flow.
*/
let authority = try self.getAuthority(forPolicy: self.kEditProfilePolicy)
/**
Acquire a token for a new account using interactive authentication
- scopes: Permissions you want included in the access token received
in the result in the completionBlock. Not all scopes are
gauranteed to be included in the access token returned.
- completionBlock: The completion block that will be called when the authentication
flow completes, or encounters an error.
*/
let thisAccount = try self.getAccountByPolicy(withAccounts: application.allAccounts(), policy: kEditProfilePolicy)
let webViewParameters = MSALWebviewParameters(parentViewController: self)
let parameters = MSALInteractiveTokenParameters(scopes: kScopes, webviewParameters: webViewParameters)
parameters.authority = authority
parameters.account = thisAccount
application.acquireToken(with: parameters) { (result, error) in
if let error = error {
self.updateLoggingText(text: "Could not edit profile: \(error)")
} else {
self.updateLoggingText(text: "Successfully edited profile")
}
}
} catch {
self.updateLoggingText(text: "Unable to construct parameters before calling acquire token \(error)")
}
}
#IBAction func refreshToken(_ sender: UIButton) {
do {
/**
authority is a URL indicating a directory that MSAL can use to obtain tokens. In Azure B2C
it is of the form `https://<instance/tfp/<tenant>/<policy>`, where `<instance>` is the
directory host (e.g. https://login.microsoftonline.com), `<tenant>` is a
identifier within the directory itself (e.g. a domain associated to the
tenant, such as contoso.onmicrosoft.com), and `<policy>` is the policy you wish to
use for the current user flow.
*/
let authority = try self.getAuthority(forPolicy: self.kSignupOrSigninPolicy)
/**
Acquire a token for an existing account silently
- scopes: Permissions you want included in the access token received
in the result in the completionBlock. Not all scopes are
gauranteed to be included in the access token returned.
- account: An account object that we retrieved from the application object before that the
authentication flow will be locked down to.
- completionBlock: The completion block that will be called when the authentication
flow completes, or encounters an error.
*/
guard let thisAccount = try self.getAccountByPolicy(withAccounts: application.allAccounts(), policy: kSignupOrSigninPolicy) else {
self.updateLoggingText(text: "There is no account available!")
return
}
let parameters = MSALSilentTokenParameters(scopes: kScopes, account:thisAccount)
parameters.authority = authority
self.application.acquireTokenSilent(with: parameters) { (result, error) in
if let error = error {
let nsError = error as NSError
// interactionRequired means we need to ask the user to sign-in. This usually happens
// when the user's Refresh Token is expired or if the user has changed their password
// among other possible reasons.
if (nsError.domain == MSALErrorDomain) {
if (nsError.code == MSALError.interactionRequired.rawValue) {
// Notice we supply the account here. This ensures we acquire token for the same account
// as we originally authenticated.
let webviewParameters = MSALWebviewParameters(parentViewController: self)
let parameters = MSALInteractiveTokenParameters(scopes: self.kScopes, webviewParameters: webviewParameters)
parameters.account = thisAccount
self.application.acquireToken(with: parameters) { (result, error) in
guard let result = result else {
self.updateLoggingText(text: "Could not acquire new token: \(error ?? "No error informarion" as! Error)")
return
}
self.accessToken = result.accessToken
self.updateLoggingText(text: "Access token is \(self.accessToken ?? "empty")")
}
return
}
}
self.updateLoggingText(text: "Could not acquire token: \(error)")
return
}
guard let result = result else {
self.updateLoggingText(text: "Could not acquire token: No result returned")
return
}
self.accessToken = result.accessToken
self.updateLoggingText(text: "Refreshing token silently")
self.updateLoggingText(text: "Refreshed access token is \(self.accessToken ?? "empty")")
}
} catch {
self.updateLoggingText(text: "Unable to construct parameters before calling acquire token \(error)")
}
}
#IBAction func callApi(_ sender: UIButton) {
guard let accessToken = self.accessToken else {
self.updateLoggingText(text: "Operation failed because could not find an access token!")
return
}
let sessionConfig = URLSessionConfiguration.default
sessionConfig.timeoutIntervalForRequest = 30
let url = URL(string: self.kGraphURI)
var request = URLRequest(url: url!)
request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
let urlSession = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: OperationQueue.main)
self.updateLoggingText(text: "Calling the API....")
urlSession.dataTask(with: request) { data, response, error in
guard let validData = data else {
self.updateLoggingText(text: "Could not call API: \(error ?? "No error informarion" as! Error)")
return
}
let result = try? JSONSerialization.jsonObject(with: validData, options: [])
guard let validResult = result as? [String: Any] else {
self.updateLoggingText(text: "Nothing returned from API")
return
}
self.updateLoggingText(text: "API response: \(validResult.debugDescription)")
}.resume()
}
#IBAction func signoutButton(_ sender: UIButton) {
do {
/**
Removes all tokens from the cache for this application for the provided account
- account: The account to remove from the cache
*/
let thisAccount = try self.getAccountByPolicy(withAccounts: application.allAccounts(), policy: kSignupOrSigninPolicy)
if let accountToRemove = thisAccount {
try application.remove(accountToRemove)
} else {
self.updateLoggingText(text: "There is no account to signing out!")
}
self.signoutButton.isEnabled = false
self.callGraphApiButton.isEnabled = false
self.editProfileButton.isEnabled = false
self.refreshTokenButton.isEnabled = false
self.updateLoggingText(text: "Signed out")
} catch {
self.updateLoggingText(text: "Received error signing out: \(error)")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(_ animated: Bool) {
if self.accessToken == nil {
signoutButton.isEnabled = false
callGraphApiButton.isEnabled = false
editProfileButton.isEnabled = false
refreshTokenButton.isEnabled = false
}
}
func getAccountByPolicy (withAccounts accounts: [MSALAccount], policy: String) throws -> MSALAccount? {
for account in accounts {
// This is a single account sample, so we only check the suffic part of the object id,
// where object id is in the form of <object id>-<policy>.
// For multi-account apps, the whole object id needs to be checked.
if let homeAccountId = account.homeAccountId, let objectId = homeAccountId.objectId {
if objectId.hasSuffix(policy.lowercased()) {
return account
}
}
}
return nil
}
/**
The way B2C knows what actions to perform for the user of the app is through the use of `Authority URL`.
It is of the form `https://<instance/tfp/<tenant>/<policy>`, where `<instance>` is the
directory host (e.g. https://login.microsoftonline.com), `<tenant>` is a
identifier within the directory itself (e.g. a domain associated to the
tenant, such as contoso.onmicrosoft.com), and `<policy>` is the policy you wish to
use for the current user flow.
*/
func getAuthority(forPolicy policy: String) throws -> MSALB2CAuthority {
guard let authorityURL = URL(string: String(format: self.kEndpoint, self.kTenantName, policy)) else {
throw NSError(domain: "SomeDomain",
code: 1,
userInfo: ["errorDescription": "Unable to create authority URL!"])
}
return try MSALB2CAuthority(url: authorityURL)
}
func updateLoggingText(text: String) {
DispatchQueue.main.async{
self.loggingText.text = text
}
}
}
After run getting error above
The sample has been updated and should be working as expected now. It is now updated to handle *.b2clogin.com and now adds sui_si and edit profile to the known authorities list.
let siginPolicyAuthority = try self.getAuthority(forPolicy: self.kSignupOrSigninPolicy)
let editProfileAuthority = try self.getAuthority(forPolicy: self.kEditProfilePolicy)
// Provide configuration for MSALPublicClientApplication
// MSAL will use default redirect uri when you provide nil
let pcaConfig = MSALPublicClientApplicationConfig(clientId: kClientID, redirectUri: nil, authority: siginPolicyAuthority)
pcaConfig.knownAuthorities = [siginPolicyAuthority, editProfileAuthority]
self.application = try MSALPublicClientApplication(configuration: pcaConfig)
and
func getAuthority(forPolicy policy: String) throws -> MSALB2CAuthority {
guard let authorityURL = URL(string: String(format: self.kEndpoint, self.kAuthorityHostName, self.kTenantName, policy)) else {
throw NSError(domain: "SomeDomain",
code: 1,
userInfo: ["errorDescription": "Unable to create authority URL!"])
}
return try MSALB2CAuthority(url: authorityURL)
}
I've been trying for a while to get oauth2 to work with Untappd api (https://untappd.com/api/docs#authentication) and have been running into dead ends with a few different oauth2 frameworks.
I've been trying P2 OAuth. I seem to be able to kick off the auth process, user signs in, I get a code back and run into trouble sending that code back to untappd for a token. I've been going off of the example from P2 oauth:
class ViewController: UIViewController {
fileprivate var alamofireManager: SessionManager?
var loader: OAuth2DataLoader?
var oauth2 = OAuth2CodeGrantNoTokenType(settings: [
"client_id": "A0******************",
"client_secret": "BA******************",
"authorize_uri": "https://untappd.com/oauth/authenticate",
"token_uri": "https://untappd.com/oauth/authorize",
"redirect_uris": ["****://oauthcallback"],
"response_type": "code",
"secret_in_body": false,
"keychain": true,
] as OAuth2JSON)
#IBOutlet var imageView: UIImageView?
#IBOutlet var signInEmbeddedButton: UIButton?
#IBOutlet var signInSafariButton: UIButton?
#IBOutlet var signInAutoButton: UIButton?
#IBOutlet var forgetButton: UIButton?
#IBAction func signInEmbedded(_ sender: UIButton?) {
if oauth2.isAuthorizing {
oauth2.abortAuthorization()
return
}
sender?.setTitle("Authorizing...", for: UIControlState.normal)
oauth2.authConfig.authorizeEmbedded = true
oauth2.authConfig.authorizeContext = self
oauth2.verbose = true
oauth2.authorize() { authParameters, error in
if let params = authParameters {
print("Authorized! Access token is in `oauth2.accessToken`")
print("Authorized! Additional parameters: \(params)")
}
else {
print("Authorization was cancelled or went wrong: \(error)") // error will not be nil
}
}
}
In the logs I seem to get back the code but the final exchange fails:
[Debug] OAuth2: Exchanging code 2010D2*********** for access token at https://untappd.com/oauth/authorize
redirecting
[Debug] OAuth2: Did exchange code for access [false] and refresh [false] tokens
Authorized! Access token is in oauth2.accessToken
Authorized! Additional parameters: ["meta": {
"error_detail" = "Missing either the client_id, redirect_url, client_secret or code parameter. Please check your request a try again.";
"error_type" = "param_error";
"http_code" = 500;
}, "response": <__NSArray0 0x174005900>(
)
]
There doesn't seem to be much help for OAuth2 and Swift 3, unless I'm looking in the wrong places. Any ideas?
Figured this out. In case anyone else runs into this. Turns out just had to modify the OAuth2CodeGrant class to add in the client_secret that wasn't being passed during the code exchange for token:
open func accessTokenRequest(with code: String, params: OAuth2StringDict? = nil) throws -> OAuth2AuthRequest {
guard let clientId = clientConfig.clientId, !clientId.isEmpty else {
throw OAuth2Error.noClientId
}
guard let redirect = context.redirectURL else {
throw OAuth2Error.noRedirectURL
}
guard let clientSecret = clientConfig.clientSecret else {
throw OAuth2Error.noClientSecret
}
let req = OAuth2AuthRequest(url: (clientConfig.tokenURL ?? clientConfig.authorizeURL), method: .GET)
req.params["code"] = code
req.params["grant_type"] = type(of: self).grantType
req.params["redirect_uri"] = redirect
req.params["client_id"] = clientId
req.params["client_secret"] = clientSecret
return req
}
See i don't know if this is an error on soundcloud side or a problem with this lib
https://github.com/OAuthSwift/OAuthSwift
So basically i'm having issues with the authentication side of things with the soundcloud api, so i'm using a webview which is programatically created and attaching this to the same view which works perfectly.
The issue is once i try to authenticate using one of the options from soundcloud either Facebook, Gmail the screen is just blank and it doesn't seem to be authenticating when using the connect with Facebook or gmail the reason why i say it isn't authenticating is because nothing is being logged in the console... and the email screen seems to be stuck as you can see in the image below... There's no callback once the authentication is done and this does work since there is output in the console when i'm testing the data being returned.
func doOAuthSoundCloud(consumerKey: String, consumerSecret: String) {
let oauthswift = OAuth2Swift(
consumerKey: consumerKey,
consumerSecret: consumerSecret,
authorizeUrl: authorizeUrl,
accessTokenUrl: accessToken,
responseType: "code"
)
oauthswift.authorize_url_handler = AuthWebViewController()
let state: String = generateStateWithLength(20) as String
oauthswift.authorizeWithCallbackURL( NSURL(string: "oauthswiftexample://oauth-callback/soundcloud")!, scope: "", state: state, success: {
credential, response, parameters in
self.saveUserData(oauthswift,credential.oauth_token)
}, failure: { error in
print(error.localizedDescription)
})
}
func saveUserData(oauthswift: OAuth2Swift, _ oauthToken: String) {
oauthswift.client.get("https://api.soundcloud.com/me?oauth_token=\(oauthToken)",
success: {
data, response in
//let json = JSON(data: data)
print("Some data")
let dataString = NSString(data: data, encoding: NSUTF8StringEncoding)
let userData = JSON(dataString!)
print(userData)
print("Some response")
print(response)
print("Extra data")
}, failure: { error in
print(error)
})
}