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.
Related
I’m trying to use ASWebAuthentication to facilitate the authentication process with Oauth 1.0. After the
user enters their credentials and approves my application, oauth provider uses the redirect_url I passed in, com.me.appName:/returnToApp
and the Safari window looks something like this:
Here's my code:
func getAuthTokenWithWebLogin(context: ASWebAuthenticationPresentationContextProviding) {
let callbackUrlScheme = “scheme:/returnToApp"
let authURL = URL(string: Constants.authURL)
guard authURL != nil else{return}
let webAuthSession = ASWebAuthenticationSession.init(url: authURL!, callbackURLScheme: callbackUrlScheme, completionHandler: { (callBack:URL?, error:Error?) in
// handle auth response
guard error == nil, let successURL = callBack else {
return
}
let oauthToken = NSURLComponents(string: (successURL.absoluteString))?.queryItems?.filter({$0.name == "code"}).first
// Do what you now that you've got the token, or use the callBack URL
print(oauthToken ?? "No OAuth Token")
})
// New in iOS 13
webAuthSession.presentationContextProvider = context
// Kick it off
webAuthSession.start()
}
I don't think it's an issue with ASWebAuthentication, since I've had the same problem when I tried using third party library OauthSwift
My bad; I didn't realize that the callbackURL must be in myApp:// format, while I had mine as myApp:/ (single slash) thinking this would also work.
My app needs to get a basecamp3 login. Hence I used the OAuth2Swift library. But unfortunately, I am unable to receive the token from basecamp even the user has authorized the app.
Below is the screenshot
I have used the following code
func createAuthRequest(){
// create an instance and retain it
let oauthswift = OAuth2Swift(
consumerKey: clientID,
consumerSecret: clientSecret,
authorizeUrl: authURL,
responseType: "token"
)
//oauthswift.authorizeURLHandler = self
oauthswift.authorizeURLHandler = SafariURLHandler(viewController: self, oauthSwift: oauthswift)
let handle = oauthswift.authorize(
withCallbackURL: URL(string: redirectURL)!,
scope: "profile", state:"") { result in //This block of code never executed
switch result {
case .success(let (credential, response, parameters)):
print(credential.oauthToken)
// Do your request
case .failure(let error):
print(error.localizedDescription)
}
}
}
The code inside withCallbackURL never executed even the user has authorized the app.
Any help regarding this is appreciated.
I found the solution the problem was I was using wrong authentication & token URL.
Following URL need to be used. I missed to add web_server in auth/token url and unfortunately Basecamp haven't mentioned the same in their documets.
let authURL = "https://launchpad.37signals.com/authorization/new?type=web_server"
let tokenURL = "https://launchpad.37signals.com/authorization/token?type=web_server"
and redirectURL = com.abc.abc:/oauth2Callback (The same redircturl need to be updated for app under basecamp developer console where com.abc.abc is bundle id of the app)
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
Im trying to implement AppAuth in iOS. basic implementation has been done. seems to be everything working fine. but im not recieving the token as expected. im getting Error Error Domain=org.openid.appauth.general Code=-4
let authorizationEndpoint : NSURL = NSURL(string: "https://accounts.google.com/o/oauth2/v2/auth")!
let tokenEndpoint : NSURL = NSURL(string: "https://www.googleapis.com/oauth2/v4/token")!
let configuration = OIDServiceConfiguration(authorizationEndpoint: authorizationEndpoint as URL, tokenEndpoint: tokenEndpoint as URL)
let request = OIDAuthorizationRequest.init(configuration: configuration, clientId: "<MyTOKEN>", scopes: [OIDScopeOpenID], redirectURL: URL(string: "http://127.0.0.1:9004")!, responseType: OIDResponseTypeCode, additionalParameters: nil)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
// appDelegate.currentAuthorizationFlow
appDelegate.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, presenting: self, callback: { (authState, error) in
if((authState) != nil){
print("Got authorization tokens. Access token: \(authState?.lastTokenResponse?.accessToken)")
}else{
print("Authorization error \(error?.localizedDescription)")
}
})
After dealing with errors and changes i figured out the problem after dealing with redirectUri and Token.
redirectUri - once you authorized with google it will generate the token and after that you should open the app. redirectUri will help with that.
This is how you can setup redirectUri
The value for iOS URL scheme wil be the scheme of your redirect URI. This is the Client ID in reverse domain name notation, e.g. com.googleusercontent.apps.IDENTIFIER. To construct the redirect URI, add your own path component. E.g. com.googleusercontent.apps.IDENTIFIER:/oauth2redirect/google. Note that there is only a single slash (/) after the scheme.
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.