Authenticating with OAuth2 on iOS - ios

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.

Related

Sign in with apple missing App Name in the permission message

I am trying to Sign in with Apple using Auth0.
But the permission message shows my app name as null
This is the permission message.
Do you want to sign in to null with you Apple ID "myAppleId#email.com"?
The logo of my app is showing correctly. It is the app name that is showing null
Where do I set the app name?
Edit: Here is how it looks -
You need to set the name and email while sending the request as requestedscopes.
Function to create a request using ASAuthorizationAppleIDProvider and initialize a controller ASAuthorizationController to perform the request.
#objc func handleAppleIdRequest() {
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = selfauthorizationController.performRequests()
}
Below function is called after successful Sign In.
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
let userIdentifier = appleIDCredential.user
let fullName = appleIDCredential.fullName
let email = appleIDCredential.email
print(“User id is \(userIdentifier) \n Full Name is \(String(describing: fullName)) \n Email id is \(String(describing: email))”) }}
If trying with Auth0, you can go with the following way to solve this problem.
Step 1.
if #available(iOS 13.0, *) {
// Create the authorization request
let request = ASAuthorizationAppleIDProvider().createRequest()
// Set scopes
request.requestedScopes = [.email, .fullName]
// Setup a controller to display the authorization flow
let controller = ASAuthorizationController(authorizationRequests: [request])
// Set delegates to handle the flow response.
controller.delegate = self
controller.presentationContextProvider = self
// Action
controller.performRequests()
}
Step 2
Implement the delegate method and get apple's authorization code there.
extension ViewController: ASAuthorizationControllerDelegate {
#available(iOS 13.0, *)
func authorizationController(controller: ASAuthorizationController,
didCompleteWithAuthorization authorization: ASAuthorization) {
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
// Success
guard let authorizationCode = appleIDCredential.authorizationCode,
let authCode = String(data: authorizationCode, encoding: .utf8) else {
print("Problem with the authorizationCode")
return
}
}
}
}
Step 3
Now you use this authCode to login to the Auth0.
func appleSignin(with authCode: String) {
Auth0.authentication().tokenExchange(withAppleAuthorizationCode: authCode)
.start { result in
switch result {
case .success(let credentials):
//LoginSucces
case .failure(let error):
//Issue with login.
}
}
}
In this method, you don't show the webview like for other login methods with Auth0.
I had posted this on Auth0 Community (the issue that I posted here), and one of their engineers replied me to approach it this way.
Check this out for more info Sign in with Apple - Auth0
In my case, the reason was an incorrectly pointed federated sign in domain, and the solution provided didnt work for me, so here is my solution:
Go to developer.apple.com
Navigate to "Certificates, Identifiers, & Profiles"
Select "Identifiers" from the left sidebar
Select "Services IDs" from the right drop down box
Select "Configure" for "sign in with apple"
Add the correct Cognito domain name under the website URLs section
The name that will show up for "null" is whatever the name of the services ID is now
I am not using Auth0 but I was facing this issue and it took me an hour to find out where that thing is set!
It was showing a dummy string I had put once, a long time ago. Now I wanted to replace it but couldn't find out whether that was in the app, code, build script, app setting, developer settings, or where the hell!
I wanted to change this:
It turned out that when you go to Certificates, Identifiers & Profiles (at https://developer.apple.com/account/resources/identifiers/list) you can actually click on that blue thing on the top right of IDentifiers and that's bringing a whole list of things!
Why the hell Apple thought that putting a sub-menu over there is better than having a nested list in the left menu bar is beyond my imagination!
So from here, you go to Service IDs and change that thingy! If it's null and you can set it. The direct link to that Shangri-la page is: https://developer.apple.com/account/resources/identifiers/list/serviceId

Athentication problems on iOS when using AppAuth and Okta

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.

How do I get to AccessToken and IdToken following successful Amazon Cognito Login from iOS

I have been using the following sample to introduce cognito login to my iOS application:
https://github.com/awslabs/aws-sdk-ios-samples/tree/master/CognitoYourUserPools-Sample
This is working well and I am at the point where I have a AWSCognitoIdentityUserPool object which I can call the currentUser() method to access the user.
What I am struggling to find is how I extract both the AccessToken and the IdToken.
In the android equivalent, the onSuccess method of the AuthenticationHandler has a CognitoUserSession argument which in turn has getIdToken() and getAccessToken().
Frustratingly, I see them output as the AuthenticationResult in json format in the output window, but I just don't know how to access them programatically?
Figured it out now:
func getSession(){
self.user?.getSession().continueOnSuccessWith { (getSessionTask) -> AnyObject? in
DispatchQueue.main.async(execute: {
let getSessionResult = getSessionTask.result
self.idToken = getSessionResult?.idToken?.tokenString
self.accessToken = getSessionResult?.accessToken?.tokenString
})
return nil
}
}

Missing required parameters, includes an invalid parameter value, parameter more than once. : client_secret

I am using p2.OAuth2 library for Oauth2 access to Linkedin. As mentioned in the usage section, I have created the following code for getting access token.
let settingsLinkedin = [
"client_id": "75afkn125135v14",
"client_secret": "R5gHlmkRtld4xge4",
"authorize_uri": "https://www.linkedin.com/uas/oauth2/authorization",
"token_uri": "https://www.linkedin.com/uas/oauth2/accessToken",
"scope": "",
"redirect_uris": ["http://www.dummyredirecturl.com/oauth.aspx"], // don't forget to register this scheme
"keychain": false ] as OAuth2JSON // the "as" part may or may not be needed
let oauth2 = OAuth2CodeGrant(settings: settingsLinkedin)
oauth2.onAuthorize = { parameters in
println("Did authorize with parameters: \(parameters)")
}
oauth2.onFailure = { error in // `error` is nil on cancel
if nil != error {
println("Authorization went wrong: \(error!.localizedDescription)")
}
}
oauth2.authConfig.authorizeEmbedded = true
oauth2.authConfig.authorizeContext = self.view.window!.rootViewController
//oauth2.authConfig.ui.backButton = <# UIBarButtonItem(...) #> // to customize go-back button
oauth2.authorize()
The problem is that when I use it with Linkedin, it gives following error:
Authorization went wrong: missing required parameters, includes an
invalid parameter value, parameter more than once. : client_secret
When I use implicit-grant, I get following error, even before the user credentials form comes up:
Authorization went wrong: Invalid redirect URL:
http://www.dummyredirecturl.com/oauth.aspx?error=unsupported_response_type&error_description=We+only+support+a+response_type+of+%22code%22%2C+but+you+passed+token
Also, when I use the above code for Facebook (of-course after changing credentials) , it works correctly.

obtain soundcloud token using p2.OAuth2

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.

Resources