I am trying to understand how oauth authentication with GITHUB works in case of IOS Apps.
I am developing an IOS App and want to use GITHUB for login authentication.
Here is flow of my app.
User registration - This is an offline process and happens outside of my app. When I create an account for user, I ask them to provide their GITHUB email address. I store this email address in our DB as userid for that user.
Get access token from GITHUB - when user opens our app we direct them to https://github.com/login/oauth/authorize using webview. Once user successfully login to GITHUB account I use https://github.com/login/oauth/access_token to get access token.
Get email address using access token - I am using https://api.github.com/user/emails to get email address of logged account using access token I got in step 2.
Verify email address: I verify email address I got in step3 against my database. If userid exists then user will be able to do transactions on our app.
Right now after GITHUB verification control is coming back to viewcontroller that has webview for GITHUB and a blank screen appears. How do I move flow to next viewcontroller ?
Here is my ViewController Code:
import UIKit
import WebKit
class Login: UIViewController, UIWebViewDelegate {
#IBOutlet weak var webview: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
let authURL = String(format: "%#?client_id=%#&redirect_uri=%#&scope=%#", arguments: [GITHUB.GITHUB_AUTHURL,GITHUB.GITHUB_CLIENT_ID,GITHUB.GITHUB_REDIRECT_URI,GITHUB.GITHUB_SCOPE])
let urlRequest = URLRequest.init(url: URL.init(string: authURL)!)
webview.loadRequest(urlRequest)
webview.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func webView(_ webview: UIWebView, shouldStartLoadWith request:URLRequest, navigationType: UIWebViewNavigationType) -> Bool{
return checkRequestForCallbackURL(request: request)
}
func checkRequestForCallbackURL(request: URLRequest) -> Bool {
//print("3. IN FUNCTION checkRequestForCallbackURL")
let requestURLString = (request.url?.absoluteString)! as String
//print("3. requestURLString=\(requestURLString)")
if requestURLString.hasPrefix(GITHUB.GITHUB_REDIRECT_URI) {
let range: Range<String.Index> = requestURLString.range(of: "?code=")!
handleGithubCode(code: requestURLString.substring(from: range.upperBound))
return false;
}
return true
}
func handleGithubCode(code: String) {
let urlString = "https://github.com/login/oauth/access_token"
if let tokenUrl = URL(string: urlString) {
let req = NSMutableURLRequest(url: tokenUrl)
req.httpMethod = "POST"
req.addValue("application/json", forHTTPHeaderField: "Content-Type")
req.addValue("application/json", forHTTPHeaderField: "Accept")
let params = [
"client_id" : GITHUB.GITHUB_CLIENT_ID,
"client_secret" : GITHUB.GITHUB_CLIENTSECRET,
"code" : code
]
req.httpBody = try? JSONSerialization.data(withJSONObject: params, options: [])
let task = URLSession.shared.dataTask(with: req as URLRequest) { data, response, error in
if let data = data {
do {
if let content = try JSONSerialization.jsonObject(with: data, options: []) as? [String: AnyObject] {
if let accessToken = content["access_token"] as? String {
self.getComposerToken(accessToken: accessToken)
}
}
} catch {}
}
}
task.resume()
}
}
func getComposerToken(accessToken: String) {
print("5. accessToken=\(accessToken)")
let def = "NO_DATA"
let composerUrl = "http://192.168.100.112/kubher/getAccessToken.php?token=\(accessToken)"
guard let url = URL(string: composerUrl) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let response = response {
//print(response)
}
if let data = data {
do {
let json = try? JSONSerialization.jsonObject(with: data, options: [])
if let dict = json as? [String: Any],
let token = dict["accessToken"] {
print("Blockchain Token:\(token)")
}
} catch {
print(error)
}
}
}.resume()
}
}
Move to next viewcontroller depends on the architecture of your code. Try the following code based on your design.
opt 1: If you need to go back to previous ViewController, just modify your getComposerToken function:
if let dict = json as? [String: Any], {
let token = dict["accessToken"] {
print("Blockchain Token:\(token)")
dispatch_async(dispatch_get_main_queue()) {
self.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
}
}
}
opt 2: On the other hand, If you are using Segue for next viewController in stoaryboard, give your segue a name(Identifier) and then follow this code:
if let dict = json as? [String: Any], {
let token = dict["accessToken"] {
print("Blockchain Token:\(token)")
dispatch_async(dispatch_get_main_queue()) {
self.performSegue(withIdentifier: "YourSegueName", sender: token)
}
}
}
Additionally, you have to override prepare method to pass data
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segueName" {
let viewController = segue.destination as? YourViewController
if let token = sender as? String {
viewController?.token = token
}
}
}
}
OPT 3: If you use push view controller after creating from storyboard, you have to give your view controller an Identifier in storyboard and then you can instantiate it using and push it using:
if let dict = json as? [String: Any], {
let token = dict["accessToken"] {
let storyboard = UIStoryboard(name: "MyStoryboardName", bundle: nil)
let abcViewController = storyboard.instantiateViewControllerWithIdentifier("YourControlleridentifier") as! YourViewController
YourViewController.token = token
navigationController?.pushViewController(YourViewController, animated: true)
}
}
Related
I have a problem to use a method developed in Swift that I want to use in objective C.
Swift 4 Class: in this function I retrieve the information of the user (like userNumber, secretCode)
#objc class MySwiftClass: HPTableViewController {
func loadMemberProfil(completion: ((_ sub : [String: AnyObject]) -> Void)!) {
//API get profile and Bearer token
let token = HPWSLoginManager.shared().saveSuccessResponse.token
let url = URL(string: "http://51.38.36.76:40/api/v1/profile")
var request = URLRequest(url: url!)
request.httpMethod = "GET"
request.addValue("Bearer \(token!)", forHTTPHeaderField: "Authorization")
//get information in token
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else { return }
do {
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! [String: AnyObject]
let sub = json["sub"] as! [String: AnyObject]
if completion != nil{
completion(sub)
}
} catch {
print("error")
}
}.resume()
}
}
for example at the end of the function I get
sub["usernumber"]! // print +224625259239
sub["secretcode"]! // print $2a$08$DIrq1iKnjkkY4KgI3Mqy7.aWC39m2aFMncfJSkom9l0yaXhGlH35m
Objective-C implementation (Objective-C.m) I want to display this information above
#import "ProjectName-Swift.h"
MySwiftClass* userProfil = [[MySwiftClass alloc]init];
[userProfil loadMemberProfil: ^(NSDictionary *sub) { // No visible #interface for 'MySwiftClass' declares the selector 'loadMemberProfil:'
userNumber = sub[#"usernumber"];
secretCode = sub[#"secretcode"];
}];
but I get this error message No visible #interface for 'MySwiftClass' declares the selector 'loadMemberProfil:' could someone help me? please :)
You need
#objcMembers class MySwiftClass: UIViewController { --- }
OR
#objc func loadMemberProfil(completion: ((_ sub : [String: AnyObject]) -> Void)!) { --- }
With this call
[userProfil loadMemberProfilWithCompletion:^(NSDictionary*dic ) {
}];
I'm downloading remote JSON data and want my loading screen to stay up until the download is complete. Once my parse method finishes running, a segue should be called to move to the next view automatically.
I've verified that my data is properly downloading and parsing. My performSegue function is even being called when I throw up a breakpoint. But the application is still not moving to the next view.
Here's where I'm calling my parse method and then immediately calling the desired segue:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
downloadSources(atURL: "https://newsapi.org/v1/sources?language=en")
performSegue(withIdentifier: "loadingFinished", sender: self)
}
For reference, if you need it, here is my parse method in its entirety:
func downloadSources(atURL urlString: String) {
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
if let validURL = URL(string: urlString) {
var request = URLRequest(url: validURL)
request.setValue("49fcb8e0fa604e7aa461ee4f22124177", forHTTPHeaderField: "X-Api-Key")
request.httpMethod = "GET"
let task = session.dataTask(with: request) { (data, response, error) in
if error != nil {
assertionFailure()
return
}
guard let response = response as? HTTPURLResponse,
response.statusCode == 200,
let data = data
else {
assertionFailure()
return
}
do {
if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
guard let sources = json["sources"] as? [[String: Any]]
else {
assertionFailure()
return
}
for source in sources {
guard let id = source["id"] as? String,
let name = source["name"] as? String,
let description = source["description"] as? String
else {
assertionFailure()
return
}
self.sources.append(Source(id: id, name: name, description: description))
}
}
}
catch {
print(error.localizedDescription)
assertionFailure()
}
}
task.resume()
}
}
Thanks in advance.
Sounds like a closure callback is what you want.
typealias CompletionHandler = ((_ success:Bool) -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
downloadSources(atURL: "www.example.com", completion: {
if success {
performSegue(withIdentifier: "loadingFinished", sender: self)
return
}
// otherwise deal with failure
})
}
func downloadSources(atURL urlString: String, completion: CompletionHandler) {
if error != nil {
completion?(false)
return
}
// finish downlaod
completion?(true)
}
I have a performance problem implementing a REST client related with long delays. Problem is not present on my Android app or any REST client adding to browsers so it is not related with backend issues.
this is my function for executing API call:
func getRecordsMethod(completion: #escaping ((_ result: Data) -> Void)){
let defaults = UserDefaults.standard
let username = defaults.object(forKey: "username") as! String
let password = defaults.object(forKey: "password") as! String
let loginString = String(format: "%#:%#", username, password).description
let loginData = loginString.data(using: String.Encoding.utf8)
let base64LoginString = loginData?.base64EncodedString()
let url = URL (string: apiURL + getPersonalBests )
var request = URLRequest(url: url!)
request.httpMethod = "GET"
request.setValue("Basic " + base64LoginString!, forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode == 200 {
completion(data!)
}
}
task.resume()
}
and how I call it in my ViewController :
override func viewDidLoad() {
super.viewDidLoad()
populateRecords()
}
func populateRecords(){
self.records.removeAll()
apiRestManager.getRecordsMethod() {
(result: Data) in
let json = JSON(data: result)
for (_, subJson) in json {
// processing response- adding to personalBestTable
}
DispatchQueue.main.async {
self.personalBestTable.reloadData()
}
}
}
The biggest issue is that after function populateRecords is being started there is delay approx 2-3 seconds after a call is visible on my backend (after that processing and table reload is made instantly)
Can anyone give me a hint how I can optimize requests speed?
Getting the data from UserDefault on main threat may cause the delay.
Change your viewDidLoad method with following code
override func viewDidLoad() {
super.viewDidLoad()
let concurrentQueue = DispatchQueue(label: "apiQueue", attributes: .concurrent)
concurrentQueue.async {
populateRecords()
}
}
Hi i am new to iOS development and i am trying to implement google translation API within my app. I found some sample code online from GitHub https://github.com/prine/ROGoogleTranslate. I downloaded the sample code and followed the instructions provided by obtaining an api key from google cloud translate and placing it within the code however the code is not working, iv looked at the comments on the GitHub site and found that it has
worked for other developers. I really don't know what i am doing wrong in the code.
ROGoogleTranslateParams.swift
import Foundation
public struct ROGoogleTranslateParams {
public init() {
}
public init(source:String, target:String, text:String) {
self.source = source
self.target = target
self.text = text
}
public var source = "de"
public var target = "en"
public var text = "Hallo"
}
/// Offers easier access to the Google Translate API
open class ROGoogleTranslate {
/// Store here the Google Translate API Key
public var apiKey = "YOUR_API_KEY"
///
/// Initial constructor
///
public init() {
}
///
/// Translate a phrase from one language into another
///
/// - parameter params: ROGoogleTranslate Struct contains all the needed parameters to translate with the Google Translate API
/// - parameter callback: The translated string will be returned in the callback
///
open func translate(params:ROGoogleTranslateParams, callback:#escaping (_ translatedText:String) -> ()) {
guard apiKey != "" else {
print("Warning: You should set the api key before calling the translate method.")
return
}
if let urlEncodedText = params.text.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) {
if let url = URL(string: "https://translation.googleapis.com/language/translate/v2?key=\(self.apiKey)&q=\(urlEncodedText)&source=\(params.source)&target=\(params.target)&format=text") {
let httprequest = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
guard error == nil else {
print("Something went wrong: \(error?.localizedDescription)")
return
}
if let httpResponse = response as? HTTPURLResponse {
guard httpResponse.statusCode == 200 else {
if let data = data {
print("Response [\(httpResponse.statusCode)] - \(data)")
}
return
}
do {
// Pyramid of optional json retrieving. I know with SwiftyJSON it would be easier, but I didn't want to add an external library
if let data = data {
if let json = try JSONSerialization.jsonObject(with: data, options:JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary {
if let jsonData = json["data"] as? [String : Any] {
if let translations = jsonData["translations"] as? [NSDictionary] {
if let translation = translations.first as? [String : Any] {
if let translatedText = translation["translatedText"] as? String {
callback(translatedText)
}
}
}
}
}
}
} catch {
print("Serialization failed: \(error.localizedDescription)")
}
}
})
httprequest.resume()
}
}
}
}
ViewController.swift
import UIKit
class ViewController: UIViewController {
#IBOutlet var text:UITextField!
#IBOutlet var fromLanguage:UITextField!
#IBOutlet var toLanguage:UITextField!
#IBOutlet var translation:UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func translate(_ sender: UIButton) {
let translator = ROGoogleTranslate()
translator.apiKey = "YOUR_API_KEY" // Add your API Key here
var params = ROGoogleTranslateParams()
params.source = fromLanguage.text ?? "de"
params.target = toLanguage.text ?? "en"
params.text = text.text ?? "Hallo"
translator.translate(params: params) { (result) in
DispatchQueue.main.async {
self.translation.text = "\(result)"
}
}
}
}
These are classes are used.
The result i get when i press the 'translate' button is the following:
Response [403] - 355 bytes
your help is appreciated. The code is available to download from the url provided
Thank you
I'm the author of the library you mentioned above :). I guess you get the 403 because your Google Api Account is not yet activated correctly. Google has changed the policy of the Translation api and its not free anymore. So you problably didn't add the credit card informations in the Api account and therefor get the 403 error?
Try this "POST" method function not the 'Get' method as you implemented -
open func translateTest(params: GoogleAITranslateParams, targetLanguage: String, callback:#escaping (_ translatedText:String) -> ()) {
guard apiKey != "" else {
return
}
var request = URLRequest(url: URL(string: "https://translation.googleapis.com/language/translate/v2?key=\(self.apiKey)")!)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue(Bundle.main.bundleIdentifier ?? "", forHTTPHeaderField: "X-Ios-Bundle-Identifier")
let jsonRequest = [
"q": params.text,
"source": "en",
"target": targetLanguage,
"format": "text"
] as [String : Any]
if let jsonData = try? JSONSerialization.data(withJSONObject: jsonRequest, options: .prettyPrinted) {
request.httpBody = jsonData
let task: URLSessionDataTask = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error == nil else {
print("Something went wrong: \(String(describing: error?.localizedDescription))")
return
}
if let httpResponse = response as? HTTPURLResponse {
guard httpResponse.statusCode == 200 else {
if let data = data {
print("Response [\(httpResponse.statusCode)] - \(data)")
}
return
}
do {
if let data = data {
if let json = try JSONSerialization.jsonObject(with: data, options:JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary {
if let jsonData = json["data"] as? [String : Any] {
if let translations = jsonData["translations"] as? [NSDictionary] {
if let translation = translations.first as? [String : Any] {
if let translatedText = translation["translatedText"] as? String {
callback(translatedText)
}
}
}
}
}
}
} catch {
print("Serialization failed: \(error.localizedDescription)")
}
}
}
task.resume()
}
}
I have a server for storing username and password data in my application. When testing the app I had everything save to the device locally using NSDefaults, but now that the app is close to being fully launched, I am trying to save them to the server instead, as it is safer that way for the user's information.
When I had it save to NSDefaults, it was easy and short work. Now however, I am trying to POST the data to the server and keep getting build errors. What do I need to change for this to work? Am I not fully understanding how POST and GET works? Thanks. Using Swift 2 as of right now, not my choice, I prefer 3, but my boss isn't letting us update it yet.
The current error is coming from the POST USER DATA TO SERVER section, where xcode claims that userNmeTxt cannot be converted into NSData. Thank you in advance.
EDIT: Error is on line 87: "Cannot convert value of type UITextField! to expected argument type NSData!"
import UIKit
class UserNameViewController: AuthorizationViewController {
#IBOutlet weak var userNameTxt: UITextField!
#IBOutlet weak var continueBtn: UIButton!
var userModel: ProfileModel!
//MARK: - SYSTEMS METHODS
override func viewDidLoad() {
super.viewDidLoad()
userNameTxt.delegate = self
userNameTxt.autocapitalizationType = .Sentences
setEnabledButton()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBarHidden = false
self.navigationItem.leftBarButtonItem = getBackButton()
self.title = ""
}
override func viewWillDisappear(animated: Bool) {
self.navigationController?.navigationBarHidden = true
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
continueBtn.layer.cornerRadius = 10
}
override func popToRoot(sender:UIBarButtonItem){
self.navigationController!.popViewControllerAnimated(true)
}
//MARK: - CHECK FOR AVALABILITY
func setEnabledButton(){
if userNameTxt.text == "" {
continueBtn.backgroundColor = UIColor.lightGrayColor()
} else {
continueBtn.backgroundColor = UIColor(colorLiteralRed: 63.0/255.0, green: 220.0/255.0, blue: 236.0/255.0, alpha: 1.0)
}
continueBtn.userInteractionEnabled = userNameTxt.text != ""
}
//MARK: - POST USER DATA TO SERVER
func postData(url: String, params: Dictionary<String, String>, completionHandler: (data: NSData?, response: NSURLResponse?, error: NSError?) -> ()) {
// Indicate download
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
let url = NSURL(string: "myPlaceholderURLgoesHere")!
// print("URL: \(url)")
let request = NSMutableURLRequest(URL: url)
let session = NSURLSession.sharedSession()
request.HTTPMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
// Verify downloading data is allowed
do {
request.HTTPBody = try NSJSONSerialization.dataWithJSONObject(params, options: [])
} catch let error as NSError {
print("Error in request post: \(error)")
request.HTTPBody = nil
} catch {
print("Catch all error: \(error)")
}
// Post the data
let task = session.dataTaskWithRequest(request) { data, response, error in
completionHandler(data: userNameTxt, response: userModel, error: error)
// Stop download indication
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
// Stop download indication
}
task.resume()
}
//MARK: - SEGUE
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "toPassword"{
let controller = segue.destinationViewController as! PasswordViewController
controller.userModel = userModel
}
}
//MARK: - IB ACTIONS
#IBAction func continuePressed(sender: AnyObject) {
userModel.userNickName = userNameTxt.text!
performSegueWithIdentifier("toPassword", sender: self)
}
}
extension UserNameViewController: UITextFieldDelegate{
func textFieldDidEndEditing(textField: UITextField) {
self.setEnabledButton()
}
}
There are a couple of things you need to change.
userNameTxt is not the username, it's the UITextField containing the username. The text you need is userNameTxt.text?
If the function is expecting Data, you have to convert your text to Data first
let task = session.dataTaskWithRequest(request) { data, response, error in
completionHandler(data: userNameTxt.text?.data(using: .utf8), response: userModel, error: error)
I assume you have to send the data to the server.
If you don't have too you can save the data in keychain access, see: SO: Keychain Access
In order to resolve the error please edit the question with the error message and line of code (if possible).
I would suggest that you use Alamofire for POST/GET (REST). To use Alamofire you need basic knowledge of Cocoapods. It's better in the long term.
NOTE: There can be two possible error outcomes when you make the request.
1) Incorrect data format or bug from your side
2) Server error due backend bug from server side.
The data can be sent from your device with POST where the data is in the BODY or HEADER of the request. Usually it is in the body (parameters in the alamofire methods).
Here is an example:
import Alamofire
...
// MARK:- Login Feature - Universal Met for login
internal static func loginWith(serverUrl: String, parameters: [String: AnyObject]?, headers: [String: String]?, notificationName: String, serviceType: LoginService)
{
Alamofire.request(.POST, serverUrl, parameters: parameters, headers: headers).responseJSON
{ (response) in
print("\n Login feature - \n")
print(" Login url - \(serverUrl)\n")
print(" Login parameters - \(parameters)\n")
print(" Login notificationName - \(notificationName)\n")
print(" Login response - \(response)\n")
EXCDataParserH.parseResponseFrom(ServiceType.Login(type: serviceType),
operation: nil,
data: response.data,
notification: notificationName)
}
}
Instead of writing the whole thing every time you make a server request, try to do as follows:
import Foundation
import SystemConfiguration
class HTTPHelper{
class func httpPostDataDic(postURL:NSURL,postString:NSString,completionHandler:#escaping (NSDictionary?, NSError?) -> Void ) -> URLSessionTask{
var responseResultData: NSDictionary = NSDictionary()
let request = NSMutableURLRequest(url:postURL as URL);
request.httpMethod = "POST";// Compose a query string
request.httpBody = postString.data(using: String.Encoding.utf8.rawValue);
print(request)
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
if error != nil
{
print("error=\(error)")
completionHandler(nil, error as NSError?)
return
}
let responseString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
if let responseString = responseString {
print("responseString = \(responseString)")
}
do {
let myJSON = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
responseResultData=myJSON!
completionHandler(responseResultData, nil)
} catch {
print(error)
}
}
task.resume()
return task
}
}
Now whenever you need to make a server POST request,in your ViewController class, do as follows:
//Requesting server
func requestServer() -> Void{
let postvariable = "Value"
let url = URL(string: "your url")!
let postString = "postkey=\(postvariable)"
HTTPHelper.httpPostDataDic(postURL: url as NSURL, postString: postString) {
(responseResult, error) -> Void in
DispatchQueue.main.async {
if error != nil{
print(error ?? "unknown")
}
else{
print(responseResult ?? "unknown result")
//Parse your response
self.parseResult(result: responseResult!);
}
}
}
}
May I ask you one thing that I didn't understand in your question.
How exactly would you save the login credentials in a server? I mean, if you save the login credentials in the server, how would you authenticate user access to these saved credentials?