ReCaptcha issue in Swift 5 - ios

i'm using this pod to use Google reCaptcha when users authenticate to an iOS app.
The captcha is shown, but not clickable.
in my loginViewController:
private var recaptcha: ReCaptcha?
private var locale: Locale = Locale(identifier: "fr-FR")
private var endpoint = ReCaptcha.Endpoint.default
override func viewDidLoad() {
super.viewDidLoad()
do {
recaptcha = try ReCaptcha(apiKey: Config.API_KEY, baseURL: URL(string: "http://localhost"), endpoint: .default, locale: locale)
}
catch {
print("error")
}
recaptcha?.configureWebView { [weak self] webview in
webview.frame = self?.view.bounds ?? CGRect.zero
}
recaptcha?.forceVisibleChallenge = true
}
func validate(){
recaptcha?.validate(on: view, resetOnError: true, completion: { (ReCaptchaResult) in
switch ReCaptchaResult {
case .error:
print("error")
case .token:
//WHAT TO DO WITH THE TOKEN HERE?
guard let userName = self.userNameTextField.text, let password = self.passwordTextField.text else {
return
}
self.authentificationPresenter.authenticate(userName: userName, password: password)
}
})
}
#IBAction func loginButtonTapped() {
validate()
}
The result is this. I don't understand how to go from here, and interpret the user's result.

Since the Captcha is loaded in a webview and to my knowledge would not be directly displayed on a ViewController is it possible that your Captcha is is somehow covered by an empty view? This may be why it is not "clickable". Either that or perhaps you need to enable interactions inside of your webview - you can try adding this when you configure the web view:
webview.isUserIntractionEnabled = true
As far as interpreting the result, if the user successfully completes the Captcha test you should receive a client token which you will use alongside your API secret key in a post request to the endpoint provided. Here is the documentation for verifying the user following the completion of a Captcha test: https://developers.google.com/recaptcha/docs/verify. Hope this helped.

Related

firebase retrieve user ID to pass it in url API calling

Hi I am a bit new to iOS development. I want to create a variable that store the UID = user.id from the firebase authentication check. Then I want to use that UID to put it in the url so that I can use that to call our backend. However, I couldn't figure out how to do it...
Xcode 11.6
swift 5
here is my swift code:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
var UID = ""
Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
self.userName.text = user.displayName
//print(user.uid)
UID = user.uid
} else {
self.userName.text = "error"
}
}
self.navigationItem.setHidesBackButton(true, animated: true)
//let url = vehicleManager.dataURL
let url = rewardManager.dataURL
print("UID is: \(UID)")
rewardManager.performRequest(dataURL: url)
claimWebKitView.load(URLRequest(url: URL(string: "https://camo.githubusercontent.com/6b254aa699df7f9464967009129c3017de721b77/68747470733a2f2f7261772e6769746875622e636f6d2f5068696c4a61792f4d50416e64726f696443686172742f6d61737465722f73637265656e73686f74732f7363617474657263686172742e706e67")!))
premiumWebKitView.load(URLRequest(url: URL(string: "https://camo.githubusercontent.com/e29d8d3316203700965cc6cc56e67b779f2845bb/68747470733a2f2f7261772e6769746875622e636f6d2f5068696c4a61792f4d5043686172742f6d61737465722f73637265656e73686f74732f636f6d62696e65645f63686172742e706e67")!))
}
debug console:
how can I pass the user.uid to my url? thanks
The reason why you are not getting any value in UID while printing is because the auth.addStateChangedListener is an async operation that is it takes some time to get your data from firebase and in that time duration your other code gets executed first. I would suggest to use completion handlers for these type of async operations. Here is the link of S.O question from where you can get more details about completion handlers.
Quick Overview :- Your completion handler gets called when the function has completed it's execution and at that part you can do your api calls by checking if you have received the correct userId or not.
after the great help from #Dev App and others, I finally figured it out I think.
So the key is to call my API in side this Firebase authentication method
func getUserName() {
Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
self.userName.text = user.displayName
} else {
self.userName.text = "error"
}
}
}
any data in that closure, such as user.displayname or user.uid is only valid in that closure.
So, I added my callApi function into that firebase authentication function. Something like below solves the problem.
func callApiFunction() {
//some code to call the api
}
func firebaseAuth() {
// some code to do authentication, and get back some data, for example:
func getUserName() {
Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
self.userName.text = user.uid
// you can pass the uid here to the callApiFunction()
callApiFunction()
} else {
self.userName.text = "error"
}
}
}
}
then you can call the func firebaseAuth() in other places, like viewdidLoad() etc.
override func viewDidLoad() {
firebaseAuth()
}

RealmSwift: Implementing one time login with MongoDB Realm in swift

I want user to login once and not have to reenter their login info everytime they open app unless they logout in the last session.
Login screen is currently displayed everytime the app is open. This is my rootview
struct AppRootView: View {
var body: some View {
AnyView {
// check if user has already logged in here and then route them accordingly
if auth.token != nil {
homeMainView()
} else {
LoginController()
}
}
}
}
currently this is what I use to login users
#objc func signUp() {
setLoading(true);
app.usernamePasswordProviderClient().registerEmail(username!, password: password!, completion: {[weak self](error) in
// Completion handlers are not necessarily called on the UI thread.
// This call to DispatchQueue.main.sync ensures that any changes to the UI,
// namely disabling the loading indicator and navigating to the next page,
// are handled on the UI thread:
DispatchQueue.main.sync {
self!.setLoading(false);
guard error == nil else {
print("Signup failed: \(error!)")
self!.errorLabel.text = "Signup failed: \(error!.localizedDescription)"
return
}
print("Signup successful!")
// Registering just registers. Now we need to sign in, but we can reuse the existing username and password.
self!.errorLabel.text = "Signup successful! Signing in..."
self!.signIn()
}
})
}
#objc func signIn() {
print("Log in as user: \(username!)");
setLoading(true);
app.login(withCredential: AppCredentials(username: username!, password: password!)) { [weak self](maybeUser, error) in
DispatchQueue.main.sync {
self!.setLoading(false);
guard error == nil else {
// Auth error: user already exists? Try logging in as that user.
print("Login failed: \(error!)");
self!.errorLabel.text = "Login failed: \(error!.localizedDescription)"
return
}
guard let user = maybeUser else {
fatalError("Invalid user object?")
}
print("Login succeeded!");
//
let hostingController = UIHostingController(rootView: ContentView())
self?.navigationController?.pushViewController(hostingController, animated: true)
}
how could I implement one time login so that users do have to login each time they open the app?
A correctly configured and initialized RealmApp class will persist the session information for you between app restarts, you can check for an existing session using the .currentUser() method from this class. So in your case something like:
if app.currentUser() != nil {
homeMainView()
} else {
LoginController()
}
While using Realm to persist login is a good idea, but I would highly
advice against using it for managing user authentication credentials such
as passwords. A better approach if you want to save sensitive information is
using KeyChain just like what Apple and password manager apps do. With a light
weight keyChain wrapper library such as SwiftKeychainWrapper You can easily
save your login credentials in the most secure way.
Here is a sample using a keyChain wrapper linked above.
With simple modification you can use this helper class to manage your sign in credentials anywhere in your app.
import SwiftKeychainWrapper
class KeyChainService {
// Make a singleton
static let shared = KeyChainService()
// Strings which will be used to map data in keychain
private let passwordKey = "passwordKey"
private let emailKey = "emailKey"
private let signInTokenKey = "signInTokenKey"
// Saving sign in info to keyChain
func saveUserSignInInformation(
email: String,
password: String,
token: String
onError: #escaping() -> Void,
onSuccess: #escaping() -> Void
) {
DispatchQueue.global(qos: .default).async {
let passwordIsSaved: Bool = KeychainWrapper.standard.set(password, forKey: self.passwordKey)
let emailIsSaved: Bool = KeychainWrapper.standard.set(email, forKey: self.emailKey)
let tokenIsSaved: Bool = KeychainWrapper.standard.set(token, forKey: self.signInTokenKey)
DispatchQueue.main.async {
// Verify that everything is saved as expected.
if passwordIsSaved && emailIsSaved && tokenIsSaved {
onSuccess()
}else {
onError()
}
}
}
}
// Retrieve signIn information for auto login
func retrieveSignInInfo(onError: #escaping() -> Void, onSuccess: #escaping(UserModel) -> Void) {
DispatchQueue.main.async {
let retrievedPassword: String? = KeychainWrapper.standard.string(forKey: self.passwordKey)
let retrievedEmail: String? = KeychainWrapper.standard.string(forKey: self.emailKey)
let retrievedToken: String? = KeychainWrapper.standard.string(forKey: self.signInTokenKey)
if let password = retrievedPassword,
let email = retrievedEmail,
let token = retrievedToken {
// Assuming that you have a custom user model named "UserModel"
let user = UserModel(email: email, password: password,token: token)
// Here is your user info which you can use to verify with server if needed and auto login user.
onSuccess(user)
}else {
onError()
}
}
}
}

iOS: ADAL Auto sign-in after sign-out

I am facing an issue with auto sign-in with ADAL v2.5.4 in my iOS App.
When a user wants to login to MSA account, we call acquireTokenWithResource with the required params and promptBehavior as AD_PROMPT_AUTO.
In the first run of the app, the user is shown the webview from which login flow is working as expected as user is getting logged in successfully.
On clicking ‘Sign Out’ in my app, I am removing all tokens that have my app’s ClientID. At this point I see that there is still one token present in the cache with ClientID ‘foci-1’.
Additionally I’m clearing the cookie storage of my app so that the webview doesn’t reuse any the cookies.
The issue arises when the user wishes to login again. When the same flow is triggered again for login, now the user is automatically signed in. In the logs I see ‘1 token found for query’.
Ideally since the user signed out earlier, they should be prompted for their credentials again.
What is the right way to handle this scenario?
Should sign-out be handled differently? Should there be any additional checks before login is retriggered? What is the impact of promptBehavior in this scenario?
This is the code I use to perform a "logout" from an app that uses ADAL.
It calls the logout endpoint to invalidate the refresh token on the server side and deletes all of the relevant cookies and keychain entries.
fileprivate var safariModal = false
fileprivate var safariHostVC: UIViewController?
public func logout(presentOn viewController: UIViewController?, modal: Bool) {
let client = "xyzzy" // Your app client id here
let redirect = "youruri://somepath/" // Your redirect URI here
ADKeychainTokenCache.defaultKeychain().removeAll(forClientId: clientid, error: nil)
if let url = URL(string:"https://login.microsoftonline.com/common/oauth2/logout?post_logout_redirect_uri=\(redirect)") {
let safari = SFSafariViewController(url: url)
safari.toolbarItems = nil
safari.delegate = self
if #available(iOS 11.0, *) {
safari.dismissButtonStyle = .close
}
guard let vc = viewController else {
return
}
self.safariHostVC = vc
self.safariModal = modal
safari.modalPresentationStyle = .overFullScreen
safari.modalTransitionStyle = .coverVertical
if modal {
vc.present(safari, animated: true, completion: nil)
} else {
vc.navigationController?.pushViewController(safari, animated: true)
}
let cookieJar = HTTPCookieStorage.shared
guard let cookies = cookieJar.cookies else { return }
let cookiesArr = Array(cookies)
for cookie: HTTPCookie in cookiesArr {
if (cookie.name == "SignInStateCookie" || cookie.name == "ESTSAUTHPERSISTENT" || cookie.name == "ESTSAUTHLIGHT" || cookie.name == "ESTSAUTH" || cookie.name == "ESTSSC") {
cookieJar.deleteCookie(cookie)
}
}
}
}
You also need to implement an SFSafariViewControllerDelegate function
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
guard let vc = self.safariHostVC else {
return
}
if self.safariModal {
vc.dismiss(animated: true, completion: nil)
} else {
vc.navigationController?.popViewController(animated: true)
}
self.safariHostVC = nil
}

not able to logout in pinterest in swift

i am integrating Pinterest login in my swift app but the issue is that i am login success fully but when i click on button logout i am not able to logout here is my code
Code
#IBAction func btnLogin(_ sender: UIButton) {
PDKClient.sharedInstance().authenticate(withPermissions: [PDKClientReadPublicPermissions,PDKClientWritePublicPermissions,PDKClientReadRelationshipsPermissions,PDKClientWriteRelationshipsPermissions], withSuccess: { (PDKResponseObject) in
self.accessToken = PDKClient.sharedInstance().oauthToken
//MARK: - Getting User Profile, use "/v1/me" to get user data in the Response object -
let parameters : [String:String] =
[
"fields": "first_name,id,last_name,url,image,username,bio,counts,created_at,account_type" //these fields will be fetched for the loggd in user
]
PDKClient.sharedInstance().getPath("/v1/me/", parameters: parameters, withSuccess: {
(PDKResponseObject) in
self.view.isUserInteractionEnabled = true
self.user = (PDKResponseObject?.user())!
print(PDKResponseObject!)
self.lblUserName.text = PDKResponseObject?.user().username
if let url = JSON(PDKResponseObject?.user().images["60x60"] as Any)["url"].string
{
self.imgProPic.sd_setImage(with: URL(string: url))
}
}) {
(Error) in
if let error = Error
{
print("\(Error)")
}
self.view.isUserInteractionEnabled = true
PDKPinError.unknown
}
}) {
(Error) in
self.view.isUserInteractionEnabled = true
}
}
#IBAction func btnLogout(_ sender: UIButton) {
/*
let cookieJar : HTTPCookieStorage = HTTPCookieStorage.shared
for cookie in cookieJar.cookies! as [HTTPCookie]{
NSLog("cookie.domain = %#", cookie.domain)
if cookie.domain == "www.pinterest.com" ||
cookie.domain == "api.pinterest.com"{
cookieJar.deleteCookie(cookie)
}
}
*/
PDKClient.clearAuthorizedUser()
}
here i have done clearAuthorizedUser() but not logged out and also i have done cookie clear code but thats not working please help me to solve this
I had a very similar issue, and I thought I might be able to help someone by posting my solution here. The app that I'm maintaining authenticates Pinterest by loading "https://api.pinterest.com/oauth/" in a UIWebView with the appropriate parameters. This authentication approach does not require the Pinterest SDK and is explained in detail here: https://developers.pinterest.com/docs/api/overview/. I was able to add a button that the user can tap to log them out of their authenticated Pinterest account. To log the user out, I removed all cookies associated with Pinterest Authentication, and removed any stored authentication token. Below is the code I use to remove the cookies:
let cookieJar: HTTPCookieStorage = HTTPCookieStorage.shared
if let cookies: [HTTPCookie] = cookieJar.cookies {
for cookie in cookies {
let domain = cookie.domain
if domain == "accounts-oauth.pinterest.com"
|| domain == ".pinterest.com"
|| domain == ".facebook.com"
|| domain == "accounts.google.com" {
cookieJar.deleteCookie(cookie)
}
}
}
After that, you will most likely need to call PDKClient.clearAuthorizedUser() to remove any stored authentication token.

iOS Swift 4 OAuth

I writing an app in Swift 4 that uses the Discogs API. As such, I require a user to have access to personal data on their Discogs account, so I am authenticating against their API using OAuthSwift. Currently, I am able to kick off the auth flow, sign in and return the an oauthToken and the oauthTokenSecret
Making a subsequent request to their https://api.discogs.com/oauth/identity I am returned a user object, so I am happy at this point I can sign in and make authenticated requests.
However, I do not understand how I can check if a user is authenticated when the app first starts up. Currently, I am not storing the response, instead I am making a call to the identity endpoint in nested callback
import UIKit
import OAuthSwift
class ViewController: UIViewController {
let oauthSwift = OAuth1Swift(
consumerKey: "foo",
consumerSecret: "bar",
requestTokenUrl: "https://api.discogs.com/oauth/request_token",
authorizeUrl: "https://www.discogs.com/oauth/authorize",
accessTokenUrl: "https://api.discogs.com/oauth/access_token"
)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
view.backgroundColor = .white
kickOffAuthFlow()
}
fileprivate func kickOffAuthFlow() {
oauthSwift.authorizeURLHandler = SafariURLHandler(viewController: self, oauthSwift: oauthSwift)
guard let callbackURL = URL(string: "foo.bar.boobaz:/oauth_callback") else { return }
oauthSwift.authorize(withCallbackURL: callbackURL, success: { (credential, response, parameters) in
_ = self.oauthSwift.client.get("https://api.discogs.com/oauth/identity", success: { (response) in
guard let dataString = response.string else { return }
print(dataString)
}, failure: { (error) in
print("error")
})
}) { (error) in
print(error.localizedDescription)
}
}
}
What is best practice in this case? How should I store these tokens and how should I ensure once the user is logged in, they aren't forced to log in the next time the app is opened (providing the token hasn't expired, however that is a separate issue I am prepared to handle at a later point)
Coming from a web development background, I was able to just store a token in session storage, on load I would then check the exp on the token and request a new one or take some other action.
I have not quite grasped how this works in iOS development yet.
You have two options to store access token in local.
UserDefault
Keychain
1. UserDefault
Use UserDefault to store token in memory. When the app gets launch, check if the token is stored in userdafault. UserDefault is used as short memory storage where you can store small data. It remains in memory if you kill the app.
let tokenIdentifier = "TokenIdentifier"
func storeAccessToken(token: String) {
UserDefaults.standard.set(token, forKey: tokenIdentifier)
}
func checkUserLogin() {
if UserDefaults.standard.value(forKey: tokenIdentifier) != nil {
print("User is Login")
}
else {
print("User need to login")
}
}
check this for learn more about userdefault
https://swift3tutorials.com/swift-3-user-defaults/
https://www.hackingwithswift.com/example-code/system/how-to-save-user-settings-using-userdefaults
2. Keychain
Userdefault is not secure. An access token is a sensitive information which should be stored in a secure place. So storing the access token in user default is not the correct choice. You must store access token in the keychain. Use SwiftKeychainWrapper pod to store token in Keychain.
let tokenIdentifier = "TokenIdentifier"
func storeAccessToken(token: String) {
KeychainWrapper.standard.set(token, forKey: tokenIdentifier)
}
func checkUserLogin() {
let token: String? = KeychainWrapper.standard.string(forKey: tokenIdentifier)
if token != nil {
print("User is Login")
}
else {
print("User need to login")
}
}

Resources