I am trying to successfully retrive a SoundCloud Token using the p2/OAuth2 lib - github: https://github.com/p2/OAuth2
Basically I recreated the classes he provides in the example Project using SoundCloud properties
class SoundCloudLoader {
static var sharedInstance = SoundCloudLoader()
class func handleRedirectURL(url: NSURL) {
sharedInstance.oauth2.handleRedirectURL(url)
}
// MARK: - Instance
let baseURL = NSURL(string: "https://api.soundcloud.com")!
var oauth2 = OAuth2CodeGrant(settings: [
"client_id": "********", //copy/pasted from soundcloud
"client_secret": "********", //copy/pasted from soundcloud
"authorize_uri": "https://soundcloud.com/connect",
"token_uri": "https://api.soundcloud.com/oauth2/token",
"scope": "non-expiring",
"redirect_uris": ["myApp://oauth2"], // registered in info.plist and gets correctly called by AppDelegate
"keychain": false,
"verbose":true
])
/** Start the OAuth dance. */
func authorize(callback: (wasFailure: Bool, error: NSError?) -> Void) {
oauth2.afterAuthorizeOrFailure = callback
oauth2.authorize()
}
}
In AppDelegate i do the following:
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject?) -> Bool {
let backlink = url.absoluteString!
println(sourceApplication)
if sourceApplication == "com.apple.mobilesafari" || sourceApplication == "com.google.chrome.ios" {
if backlink.rangeOfString("myApp://oauth2") != nil {
SoundCloudLoader.handleRedirectURL(url)
}
}
}
Considering the Log File it seems like the first Request is successfully done and it breaks when calling the token_uri:
OAuth2: Initialized with client id XXX
OAuth2: No access token, maybe I can refresh
OAuth2: I don't have a refresh token, not trying to refresh
OAuth2: Authorizing against https://soundcloud.com/connect?client_id=XXX&redirect_uri=XXX&scope=non-expiring&response_type=code&state=xxx
Optional("com.apple.mobilesafari") //println of source App in AppDelegate Method
OAuth2: Handling redirect URL xxx?code=xxx&state=xxx#
OAuth2: Authorizing against https://api.soundcloud.com/oauth2/token?code=***&grant_type=authorization_code&client_id=xxx&redirect_uri=xxx&scope=non-expiring&state=xxx
OAuth2: Adding “Authorization” header as “Basic client-key:client-secret”
OAuth2: Exchanging code xxx with redirect xxx for access token at https://api.soundcloud.com/oauth2/token
OAuth2: Did not get access token expiration interval
OAuth2: Authorization error: invalid_client.
Error Domain=OAuth2ErrorDomain Code=605 "Authorization error: invalid_client." UserInfo=0x7f8aa05c6700 {NSLocalizedDescription=Authorization error: invalid_client.}
As I am a beginner in "OAuth things" does this logfile imply that the lib crashes because soundcloud does not give a expiration date for the token (in some blog post they said that they are currently using non-expiring tokens), or does it suddenly crash because the client-id is wrong? (which it is not, as it is the exact one in the soundcloud-dev backend and it is able to obtain a req-code).
Any hints would be greatly appreciated.
Try to add
oauth2.secretInBody = true
I had the same problem and that's made the trick for me.
Related
I'm using a service that provides an OAuth2.0 authentication. This are the steps i need:
Open a URL with user Id as params
User approves my app (which is correctyle registered).
The user is redirected to a RedirectUri, with access token in the hash.
The third point is my main problem.
I've implemented the OAuth with microsoft libraries and everything works fine. But I cant use them here so I'm trying https://github.com/OAuthSwift/OAuthSwift this one.
This is my code:
private func authenticationService() {
// create an instance and retain it
let oauthswift = OAuth2Swift(
consumerKey: "xx",
consumerSecret: "xxx",
authorizeUrl: "//myurl + userId",
responseType: "token"
)
oauthswift.authorizeURLHandler = OAuthSwiftOpenURLExternally.sharedInstance
let handle = oauthswift.authorize(
withCallbackURL: "???",
scope: "", state:"") { result in
switch result {
case .success(let (credential, response, parameters)):
print(credential.oauthToken)
// Do your request
case .failure(let error):
print(error.localizedDescription)
}
}
}
This open correctly my Safari but then I'm redirected to the URI with access token in the hash and nothing happened.
The main problem here is that I've a redirect uri so I guess the callback URL is not called? And this is not opening a sheet but it is redirecting to Safari. And I dont like this approach.
How can I perform OAuth2.0 in swift with the steps above? How can I get the access token from an url? What is the best library and how can I get the most of it?
Update:
This is my code for stackExchange:
let request: OAuth2Request = .init(authUrl: "https://stackexchange.com/oauth/dialog?client_id=<MYCLIENTID>&scope=private_info&redirect_uri=https://stackexchange.com/oauth/login_success",
tokenUrl: "https://stackoverflow.com/oauth/access_token/json",
clientId: "<MYCLIENTID>",
redirectUri: "https://stackexchange.com/oauth/login_success",
clientSecret: "",
scopes: [])
The OAuth domain in stack apps is => stackexchange.com
So i've added in my URL Types the following: redirect-uri://<stackexchange.com> (even without <>)
But everytimes I approve my app i'm stacked in the "Authorizing application" which contains my token and i'm not redirected.
If you are targeting iOS 13 you can use the new AuthenticationServices library provided by Apple.
It will work on both macOS and iOS.
Maybe this would help other developers, I create a simple and small swift package to handle OAuth2 in Swift, you can check the demo project it works very well 👍
https://github.com/hadiidbouk/SimpleOAuth2
Edit:
You are passing the wrong URLs, they should be like this
let request: OAuth2Request = .init(authUrl: "https://stackoverflow.com/oauth",
tokenUrl: "https://stackoverflow.com/oauth/access_token/json",
clientId: "<<your client id>>",
redirectUri: "redirect-uri://stackexchange.com",
clientSecret: "<<your client secret>>",
scopes: ["private_info"])
In the below code:
private func authenticationService() {
// create an instance and retain it
let oauthswift = OAuth2Swift(...)
you don't retain oauthswift (nor handle). They will be deallocated as soon as you finish executing the authenticationService function.
You need to store references to oauthswift and handle outside the function (at the class level).
let oauthswift: OAuth2Swift
let handle: ...
init() {
oauthswift = OAuth2Swift(
consumerKey: "xx",
consumerSecret: "xxx",
authorizeUrl: "//myurl + userId",
responseType: "token"
)
...
}
private func authenticationService() {
handle = oauthswift.authorize(...)
}
I've been using p2-oauth2 library earlier to be able to log in through a safariViewController, but since the latest iOS version (11.3) I found out that my app were crashing all the time when the user tries to log in. I didn't get any error messages, but after a while I found out that SFAuthenticationSessions is the way to go when using SSO (single sign on).
My old code were pretty much like this (Using p2_oauth2):
static var oauth2 = OAuth2CodeGrant(settings: [
"client_id": "myClientID",
"authorize_uri": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
"token_uri": "https://login.microsoftonline.com/common/oauth2/v2.0/token",
"scope": "User.Read Mail.Read Calendars.ReadWrite Calendars.Read Calendars.Read.Shared Offline_access",
"redirect_uris": ["myRedirectURI"],
"keychain": true
])
func loginToOffice365(completion: #escaping (_ error: Error? ) -> ()) {
var userDataRequest: URLRequest {
var request = URLRequest(url: URL(string: "https://graph.microsoft.com/v1.0/me/")!)
request.setValue("Bearer \(OauthManager.oauth2.accessToken ?? "")", forHTTPHeaderField: "Authorization")
return request
}
alamofireManager.request(userDataRequest).validate().responseJSON {
(response) in
switch response.result {
case .success( _):
//Handle user information
completion(nil)
case .failure(let error):
completion(error)
}
}
}
I tried to implement in SFAuthenticationSession in my project, and it was requiring a URL as a parameter. So I have been searching for a while for a Microsoft URL to make it possible to send in clientId, scope, and redirectURI in the same URL. And here's the result so far:
let url = URL(string: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?state=78E99F6B&response_type=code&scope=User.Read+Mail.Read+Calendars.ReadWrite+Calendars.Read+Calendars.Read.Shared&redirect_uri=MYREDIRECTURI&client_id=MYCLIENTID")!
OauthManager.authenticationSession = SFAuthenticationSession(url: url, callbackURLScheme: nil, completionHandler: { (successUrl: URL?, error: Error?) in
if let error = error {
print(error)
completion(error)
} else {
var accessToken = ""
if let absolutString = successUrl?.absoluteString, let urlComponents = URLComponents(string: absolutString)?.query {
accessToken = urlComponents
}
print(accessToken)
completion(nil)
}
})
OauthManager.authenticationSession?.start()
So now I finally received an access token from Microsoft. But where should I go from here? How do I get refresh tokens, make it possible to start calling Microsoft graph API calls?
If you know any better solution or any advice I'll be glad to receive them! This is my first project using login, because I'm fairly new to Swift.
Update:
I can also mention that Microsoft documentation recommends using these libraries:
Microsoft Authentication Library (MSAL) client libraries are
available for .NET, JavaScript, Android, and Objective-C. All
platforms are in production-supported preview, and, in the event
breaking changes are introduced, Microsoft guarantees a path to
upgrade.
Server middleware from Microsoft is available for .NET Core and
ASP.NET (OWIN OpenID Connect and OAuth) and for Node.js (Microsoft
Azure AD Passport.js).
The v2.0 endpoint is compatible with many third-party authentication
libraries.
https://developer.microsoft.com/en-us/graph/docs/concepts/auth_overview
I've tried MSAL and AppAuth, but they just didn't gave me any response back.
#Paulw11 found the answer.
I was using a method, which worked fine up until XCode 9.3 and iOS 11.3:
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
return true
}
But I had to change to the following method to make it work:
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
return true
}
This is fixed in Xcode 9.4 and worked fine as we tested it. There is indeed a problem on Xcode 9.3
I have a simple iOS Swift app loosely based on the AppAuth-iOS example (https://github.com/openid/AppAuth-iOS) as well as Okta OAuth sample (https://github.com/oktadeveloper/okta-openidconnect-appauth-ios). I am not using Service Discovery nor authomatic token aquisition (i.e. not using authStateByPresentingAuthorizationRequest).
My sample works against Azure AD but does not work against Okta. I am able to log in and am authenticated and redirected back to my mobile app (AppDelegate.application()) but then the flow does not return to my OIDAuthorizationService.present() completion block.
Here is some code:
#IBAction func signInButton(_ sender: Any) {
// select idp
switch selectedIdentityProvider! {
case "Azure AD":
selectedAuthConfig = AzureAdAuthConfig()
case "Okta":
selectedAuthConfig = OktaAuthConfig();
default:
return
}
appAuthAuthorize(authConfig: selectedAuthConfig!)
}
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: authConfig.scope, 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 tokens. Access token: \(String(describing: self.authState?.lastAuthorizationResponse.authorizationCode))")
self.doTokenRequest()
} else {
self.authState = nil
self.logMessage(message: "Authorization error: \(String(describing: error?.localizedDescription))")
}
})
}
I could rewrite the code to use authStateByPresentingAuthorizationRequest() to see if it works but am a bit leery as this code works against Azure AD. Any suggestions?
Update 1
I forgot to mention that I have a working Android/Java example going against the same Okta definitions and working like a charm.
Update 2
I did rewrite the code to use authStateByPresentingAuthorizationRequest() against Okta and am getting the same result (i.e. getting stuck after redirect back to my app). I tested this against Azure AD and it works Ok.
Resolved. I guess the problem was that the redirect URL defined in Okta was mixed case. Android AppAuth implementation does not mind but iOS AppAuth implementation does. Changed redirect URL in Okta to lower case only, changed redirect Uri paramter passed in to lower case only and bing, all works great. Thanks #jmelberg for pointing me in this direction - by debugging resumeAuthorizationFlow(with: url) I was able to see the exact behaviour and why the call returned a False.
I'm currently trying use the outlook mail service to obtain contacts/appointments and mails. However I've stumbled upon a problem.
I'm using OAuth2Swift as library to make all my OAuth calls since I'm integrating multiple services.
I created a URL schemelike described in their README
Then I created a Constants file which looks like this
struct Consumer {
let consumerKey: String
let consumerSecret: String
let authorizeURL: String
let accessTokenURL: String
let responseType: String?
let requestTokenURL: String?
}
let Outlook = Consumer(
consumerKey: "",
consumerSecret: "",
authorizeURL: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
accessTokenURL: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
responseType: "code",
requestTokenURL: nil)
I created an application for outlook on https://apps.dev.microsoft.com
generated my key and secret and filled them in inside my application.
I added the mobile application platform to my app. Which tells me to use the redirect URI urn:ietf:wg:oauth:2.0:oob
So my code to authorise looks like this
#IBAction func btn_Outlook(_ sender: Any) {
let oauthOU = OAuth2Swift(
consumerKey: Outlook.consumerKey,
consumerSecret: Outlook.consumerSecret,
authorizeUrl: Outlook.authorizeURL,
accessTokenUrl: Outlook.accessTokenURL,
responseType: Outlook.responseType!)
oauthOU.authorizeURLHandler = SafariURLHandler(viewController: self, oauthSwift: oauthOU)
oauthOU.authorize(
withCallbackURL: "urn:ietf:wg:oauth:2.0:oob",
scope: "https://outlook.office.com/Mail.ReadWrite https://outlook.office.com/Mail.Send https://outlook.office.com/Calendars.ReadWrite https://outlook.office.com/Contacts.ReadWrite https://outlook.office.com/Tasks.ReadWrite",
state: state,
success: { credential, response, parameters in
print("logged in with \(credential), with response \(response) and parameters \(parameters)")},
failure: { error in
print("error occured \(error.localizedDescription)")
}
)
}
when I run the code I first get a screen to enter my mail/password. When I enter my mail it will redirect me to a different page/portal where I can enter my password. When I've entered my password it will show me the permissions screen.
as soon as I hit yes it will give me an error saying "Safari cannot open the page because the address is invalid." I'm pretty sure this has to do with the redirect URI but I'm not sure what to do to actually fix this.
I'm hoping someone is able to help me out with this!
I think you forgot to specify the identifier urn:ietf:wg:oauth:2.0:oob in your URL scheme settings (see first image: there is no identifier set)
I think you forgot to handle redirect URI in AppDelegate from OAuthSwift library! You've to handle the callback in AppDelegate as below.
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
if (url.host == "oauth-callback") {
OAuthSwift.handle(url: url)
}
return true
}
I am currently trying to authorize my users with OAuth2. I am currently using the following library: https://github.com/p2/OAuth2
let oauth2 = OAuth2CodeGrant(settings: [
"client_id": "my-id",
"authorize_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://www.googleapis.com/oauth2/v3/token",
"scope": "profile", // depends on the API you use
"redirect_uris": ["com.TestAuthorizeApp:/oauth2Callback"],
])
//let oauth2 = OAuth2CodeGrant(settings: settings)
oauth2.onAuthorize = { parameters in
print("Did authorize with parameters: \(parameters)")
}
oauth2.onFailure = { error in // `error` is nil on cancel
if let error = error {
print("Authorization went wrong: \(error)")
}
}
oauth2.authConfig.authorizeEmbedded = false
oauth2.authorize()
When I run this it loads up google in the browser and I am able to sign in. It then asks me about the permissions I have declared in the scope and that works fine. I click ok open and it redirects me back to my app.
However when I run this code again I am expecting that the access token has been stored in the key chain. However this doesn't seem to be working.
I have looked inside the source code and found the following check: tryToObtainAccessTokenIfNeeded which always returns false. This means I get the page again where I need to click 'Allow'.
I was wondering if someone could help me figure out why it's not saving anything in the keychain. Also does this mean the user is not really being authenticated?
Thanks.
===
Edit
Have added oauth2.verbose = true as per Pascal's comment. I get the following output.
OAuth2: Looking for items in keychain
OAuth2: No access token, maybe I can refresh
OAuth2: I don't have a refresh token, not trying to refresh
Which is what I thought was happening. However I am still unsure as to why it's not saving / finding anything in the keychain.
=====
Edit 2
It turns out that I wasn't actually getting an access token back at all. Please see this conversation: https://github.com/p2/OAuth2/issues/109 and my answer below.
With the help from Pascal here: https://github.com/p2/OAuth2/issues/109 I have managed to get it working. Turns out that I wasn't implementing step: '3 Authorize the User' as I should have been.
So a complete solution is:
Inside my view controller I have the following:
let OAuth2AppDidReceiveCallbackNotification = "OAuth2AppDidReceiveCallback"
override func viewDidLoad() {
super.viewDidLoad()
// This notification is for handling step 3 in guide.
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.handleRedirect(_:)), name: OAuth2AppDidReceiveCallbackNotification, object: nil)
}
func authoriseUser {
let oauth2 = OAuth2CodeGrant(settings: [
"client_id": "my-id", // Use own client_id here
"authorize_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://www.googleapis.com/oauth2/v3/token",
"scope": "profile", // depends on the API you use
"redirect_uris": ["com.TestAuthorizeApp:/oauth2Callback"],
])
//let oauth2 = OAuth2CodeGrant(settings: settings)
oauth2.onAuthorize = { parameters in
print("Did authorize with parameters: \(parameters)")
}
oauth2.onFailure = { error in // `error` is nil on cancel
if let error = error {
print("Authorization went wrong: \(error)")
}
}
oauth2.authConfig.authorizeEmbedded = false
oauth2.authorize()
}
// This method gets called by notification and is the last thing we need to do to get our access token.
func handleRedirect(notification: NSNotification) {
oauth2.handleRedirectURL(notification.object as! NSURL)
}
The above code should handle sending you to the google web page where you can log in and then click allow.
Now you need to handle returning to the app in the app delegate:
let OAuth2AppDidReceiveCallbackNotification = "OAuth2AppDidReceiveCallback"
func application(application: UIApplication,
openURL url: NSURL,
sourceApplication: String?,
annotation: AnyObject) -> Bool {
// you should probably first check if this is your URL being opened
NSNotificationCenter.defaultCenter().postNotificationName(OAuth2AppDidReceiveCallbackNotification, object: url)
return true
}
Hopefully this will help anyone else who might be having issues trying to get an access token.