ASWebAuthentication and SFAuthentication login problem after logout on iOS 11 and iOS 12, not able to invalidate session - swift

I am using ASWebAuthentication and SFAuthenticationto authenticate on a OAuth2 server with grant_type : authorization_code.
Everything work perfect except of:
user login successfully
Logout by revoking access token and refresh token with status code :200
Tested that the revocation works <------
User press login again and opens ASWebAuthentication or SFAuthentication according the iOS version and just goes directly in the app like the session is validating that the tokens are valid somehow.
On iOS 13 ASWebAuthentication provides prefersEphemeralWebBrowserSession = true which solves the issue but for iOS 11/12 it looks like it is nothing I can do (except that the issue is probably on BackEnd)
When I am logging in I am passing the parameters prompt:login but still it doesn't help.
Questions
Is there something that I can do to invalidate the session on ASWebAuthentication and SFAuthentication on iOS 11/12 similar to iOS 13 prefersEphemeralWebBrowserSession? (I haven't found something on the documentation. Apple doesn't allow anything)
Is this an issue that can be solved on frontend (iOS) or it is a backend issue?
Thank you
Providing the code just for documentation
print("Auth-Login : Process: Authenticate user")
let state = String().createState(length: 4)
let codeVerifier = String().createCodeVerifier()
let codeChallenge = codeVerifier.pkceEncode.base64UriEncode
let parameters = ["state=\(state)", "code_challenge=\(codeChallenge)"]
let url = createUrl(parameters: parameters)
guard let authURL = url else { return }
DispatchQueue.main.async {
self.delegate?.removeLoader()
if #available(iOS 12.0, *) {
print("Auth-Login : Process: Run ASWebAuthenticationSession")
self.webAuthSession = ASWebAuthenticationSession(url: authURL, callbackURLScheme: "no.bilkollektivet.app") { (callbackUrl, error) in
print(callbackUrl)
if let error = error {
completionHandler(nil, nil, error)
} else {
let result = self.getCodeFromCallbackUrl(url: callbackUrl, state: state)
completionHandler(result.code, codeVerifier, result.error)
}
}
if #available(iOS 13.0, *) {
self.webAuthSession.presentationContextProvider = self
self.webAuthSession.prefersEphemeralWebBrowserSession = true
}
self.webAuthSession.start()
} else {
print("Auth-Login : Process: Run SFAuthenticationSession")
self.sfAuthSession = SFAuthenticationSession(url: authURL, callbackURLScheme: "no.bilkollektivet.app") { (callbackUrl, error) in
if let error = error {
completionHandler(nil, nil, error)
} else {
let result = self.getCodeFromCallbackUrl(url: callbackUrl, state: state)
completionHandler(result.code, codeVerifier, result.error)
}
}
self.sfAuthSession.start()
}
}

Related

iOS Firebase Twitter Login asks for Permission Every Time

I am able to create an account and login via Twitter; however, with each login attempt the user is taken to a Twitter web page where they have to login with their credentials. Given this happens every time, it negates the value of this kind of social login. I have Facebook and Google logins working without this issue on Firebase. Below is the code used to login:
private func loginWithTwitter() {
TWTRTwitter.sharedInstance().logIn { (session, error) in
guard error == nil && session != nil else {
self.spinner.dismiss()
Utilities.displayAlert(title: "Login Error", msg: error?.localizedDescription ?? "Could not login with Twitter at this time.", controller: self)
return
}
let credential = TwitterAuthProvider.credential(withToken: session!.authToken, secret: session!.authTokenSecret)
Auth.auth().signInAndRetrieveData(with: credential, completion: { (result, error) in
if let error = error {
self.spinner.dismiss()
self.present(self.sharedManager.getAlertWith(title: "Login Error", andMsg: error.localizedDescription), animated: true)
return
}
self.loadUserInfo()
})
}
}
I should also add that I do have the use for logins box checked on the Twitter Developers page.
UPDATE:
Based on the post of Pratik below and here is a modified version that does seem to work. However, I am not sure if it's the right long-term answer. I wonder about token expiration for example. Thoughts on if there is a better way than this?
private func loginWithTwitter() {
let token = TWTRTwitter.sharedInstance().sessionStore.session()?.authToken
let secret = TWTRTwitter.sharedInstance().sessionStore.session()?.authTokenSecret
guard token != nil && secret != nil else {
self.present(self.sharedManager.getAlertWith(title: "Login Error", andMsg: "Could not login with Twitter. Please try again."), animated: true)
return
}
let credential = TwitterAuthProvider.credential(withToken: token!, secret: secret!)
Auth.auth().signInAndRetrieveData(with: credential, completion: { (result, error) in
if let error = error {
self.spinner.dismiss()
self.present(self.sharedManager.getAlertWith(title: "Login Error", andMsg: error.localizedDescription), animated: true)
return
}
self.loadUserInfo()
})
}
I'm not sure with will help or not, but im using twitter login in my app and once i authenticate with twitter i used below function to when user open app again for validation.
func silentLoginWithTwitter()
{
let client = TWTRAPIClient.withCurrentUser()
let request = client.urlRequest(withMethod: "GET", urlString: "https://api.twitter.com/1.1/account/verify_credentials.json", parameters: ["include_email": "true", "skip_status": "true"], error: nil)
client.sendTwitterRequest(request, completion: { (response:URLResponse?, data:Data?, error:Error?) in
if error == nil
{
// Open main page of application
}else
{
print("Error: Twitter : \(String(describing: error))")
// Open Login page again
}
})
}
OR you can check session auth
if let authSession = TWTRTwitter.sharedInstance().sessionStore.session()?.authToken {
TWTRTwitter.sharedInstance().sessionStore.isValidOauthToken(authSession)
}
Hope this will help

How to get username from AWS Cognito - Swift

Q & A Style: See Answer Below
How Can I get the username from a user logged in with Cognito?
I've done this and my user is logged in, now what?
AWSAuthUIViewController.presentViewController(
with: self.navigationController!,
configuration: config, completionHandler: { (provider: AWSSignInProvider, error: Error?) in
if error == nil {
//get parameters
}
} else {
print(error as Any)
}
})
}
Prerequisites:
App registered with MobileHub
Cognito Setup in MobileHub
Mobilehub integrated with Swift Project using AWS SDK
If you're like me, you did this with little to no difficulty and now you're stuck trying to get the username and other parameters from the logged in user. There are a lot of answers, but thus far, I haven't stumbled upon one that gets you all the way there.
I was able to piece this together from various sources:
func getUsername() {
//to check if user is logged in with Cognito... not sure if this is necessary
let identityManager = AWSIdentityManager.default()
let identityProvider = identityManager.credentialsProvider.identityProvider.identityProviderName
if identityProvider == "cognito-identity.amazonaws.com" {
print("************LOGGED IN WITH COGNITO************")
let serviceConfiguration = AWSServiceConfiguration(region: .USWest2, credentialsProvider: nil)
let userPoolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: "YourClientID", clientSecret: "YourSecretKey", poolId: "YourPoolID")
AWSCognitoIdentityUserPool.register(with: serviceConfiguration, userPoolConfiguration: userPoolConfiguration, forKey: "YourPoolName (typically formatted as YourAppName_userpoool_MOBILEHUB_12345678")
let pool = AWSCognitoIdentityUserPool(forKey: "YourPoolName")
// the following line doesn't seem to be necessary and isn't used so I've commented it out, but it is included in official documentation
// let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .USWest2, identityPoolId: "YourPoolID", identityProviderManager:pool)
if let username = pool.currentUser()?.username {
print("Username Retrieved Successfully: \(username)")
} else {
print("Error getting username from current user - attempt to get user")
let user = pool.getUser()
let username = user.username
print("Username: \(username)")
}
}
}
To get your ClientID, Secret Key, and PoolID, check your awsconfiguration.json
To get your PoolName, login to MobileHub, and in your project's backend, go to User Sign in, click Email and Password, then click Edit in Cognito. The following page will have your Pool Name as "YourAppName_userpool_MOBILEHUB_12345678"
Edit: To get all of the attributes as well:
if let userFromPool = pool.currentUser() {
userFromPool.getDetails().continueOnSuccessWith(block: { (task) -> Any? in
DispatchQueue.main.async {
if let error = task.error as NSError? {
print("Error getting user attributes from Cognito: \(error)")
} else {
let response = task.result
if let userAttributes = response?.userAttributes {
print("user attributes found: \(userAttributes)")
for attribute in userAttributes {
if attribute.name == "email" {
if let email = attribute.value {
print("User Email: \(email)")
}
}
}
If you're using Cognito User Pools, you can use this:
import AWSUserPoolsSignIn
AWSCognitoUserPoolsSignInProvider.sharedInstance()
.getUserPool()
.currentUser()?
.username

requestAccessToAccounts not performing in Xcode9

I have an app that uses a Twitter project to access tweets. It worked fine in Xcode 8 simulators but when I tried to run it on my device which is using iOS 11.2.1, I have problems. Also, using XCode9 simulators is causing the same issue. The app is not asking for permission to access Twitter with a popup like it was before. The following code handles the request:
func performTwitterSLRequest(_ request: SLRequest, handler: #escaping (PropertyList?) -> Void) {
if let account = twitterAccount {
request.account = account
request.perform { (jsonResponse, httpResponse, _) in
var propertyListResponse: PropertyList?
if jsonResponse != nil {
propertyListResponse = try? JSONSerialization.jsonObject(with: jsonResponse!, options: .mutableLeaves)
if propertyListResponse == nil {
let error = "Couldn't parse JSON response."
self.log(error)
propertyListResponse = error
}
} else {
let error = "No response from Twitter."
self.log(error)
propertyListResponse = error
}
self.synchronize {
self.captureFollowonRequestInfo(propertyListResponse)
}
handler(propertyListResponse)
}
} else {
let accountStore = ACAccountStore()
let twitterAccountType = accountStore.accountType(withAccountTypeIdentifier: ACAccountTypeIdentifierTwitter)
accountStore.requestAccessToAccounts(with: twitterAccountType, options: nil) { (granted, _) in
if granted {
if let account = accountStore.accounts(with: twitterAccountType)?.last as? ACAccount {
twitterAccount = account
self.performTwitterSLRequest(request, handler: handler)
} else {
let error = "Couldn't discover Twitter account type."
self.log(error)
handler(error)
}
} else {
let error = "Access to Twitter was not granted."
self.log(error)
handler(error)
}
}
}
}
I am just getting: "Access to Twitter was not granted." Has requestAccessToAccounts been deprecated or could there be another cause of the problem?
Indeed, support for accessing Twitter (or Facebook, etc) accounts using the ACAccount framework has been removed as of iOS 11.
Per the release notes for iOS 11 build 15a5278f:
Social accounts have been removed from Settings in iOS 11. Third-party apps will no longer have access to those signed-in accounts.
Twitter has posted a note on how to work around this using their TwitterKit framework. However, they've recently (April 2018) announced that they are discontinuing support for it. You may need to look for other (ie. open source) options for this, as it should be still possible using their API.

How do I get my AWS Cognito access token for updating user attributes?

I have the structure set up for updating user attributes, in this case the preferred username to use as an alias for signing in.
var attributes = [AWSCognitoIdentityUserAttributeType]()
let prefUsername = AWSCognitoIdentityUserAttributeType();
prefUsername?.name = "preferred_username";
prefUsername?.value = usernameField.text!;
attributes.append(prefUsername!);
let attributesRequest = AWSCognitoIdentityProviderUpdateUserAttributesRequest();
attributesRequest.userAttributes = attributes;
idProvider?.updateUserAttributes(attributesRequest)
Only thing I have no idea how to do is get the access token. I've looked in as much documentation as I could think of but I had no luck finding place to get access token.
You can use the api to initiate auth and get an AccessToken from the AuthenticationResult.
https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_InitiateAuth.html
/// Function to retreive the current token for the logged in user.
///
/// - Parameter completion: A completion block with an error or the token. Called back on the main thread.
public func getJWT(completion: #escaping((_ error: Error?, _ token: AWSCognitoIdentityUserSessionToken?) -> Void)) {
guard let user = self.pool.currentUser() else {
let nsError = NSError(domain: "JWT Error", code: 500, userInfo: ["message": "No Logged in user"])
completion(nsError, nil)
return
}
user.getSession().continueWith { (task) -> Any? in
DispatchQueue.main.async {
if let error = task.error {
completion(error, nil)
}else if let token = task.result?.idToken {
completion(nil, token)
}else {
completion(nil, nil)
}
}
}
}
Where self.pool is the AWSCognitoIdentityUserPool you hopefully set up correctly.
You would have to authenticate first to establish a session with Cognito User Pools. That session would contain an access token which you can then pass to every subsequent request. I see you are using the low level SDK methods. Here is a sample in swift for SignIn:
https://github.com/awslabs/aws-sdk-ios-samples/tree/master/CognitoYourUserPools-Sample/Swift

Facebook Login Error - Xcode 8 GM

I am using the latest Facebook SDK and I get this error when I run the block of code below: Facebook signup error - The operation couldn’t be completed. (com.facebook.sdk.login error 308.)
Here is my code:
func signupWithFacebook() {
FBSDKLoginManager().logIn(withReadPermissions: ["public_profile"], from: self) { (result, error) in
if let error = error {
print("Facebook signup error - \(error.localizedDescription)")
} else if result != nil {
let credential = FIRFacebookAuthProvider.credential(withAccessToken: FBSDKAccessToken.current().tokenString)
self.facebookSignup = true
self.addUserToAuth(credential, twitterUserID: "")
}
}
}
I Figured it out! It has to do with the way Apple deals with Keychain. All you have to do is go into the "Compatibilities" tab under the target for your app and turn "Keychain Sharing" on. Here is a more fulfilling answer.