OAuth2 icon indicating copy to clipboard operation
OAuth2 copied to clipboard

HowTo: Use OAuth2PasswordGrant + alamofire + own authorize loginscreen

Open ItsMeSandu opened this issue 7 years ago • 7 comments

Sorry for this question but I tryhard since 2 days 🙄

I have 4 components:

  • ViewController
  • LoginViewController
  • OAuthHandler
  • OAuth2RetryHandler (from alamofire)

ViewController:

class ViewController: UIViewController {
	
    var loginHandler: LoginHandler?

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
		
	loginHandler = LoginHandler(clientId: CLIENT_ID, clientSecret: CLIENT_SECRET)
	loginHandler?.authorize(view: self, completion: { (response) in
	    print(response)
	})
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

LoginViewController: currently just a blank UIViewController

OAuth2RetryHandler: is just copied from the sample

OAuthHandler:

class OAuthHandler: OAuth2PasswordGrantDelegate {
    var oauth2: OAuth2PasswordGrant
    fileprivate var alamofireManager: SessionManager
	
    public init(clientId: String, clientSecret: String) {
        oauth2 = OAuth2PasswordGrant(settings: [
		"client_id": clientId,
		"client_secret": clientSecret,
		"token_uri": TOKEN_URI,
		"keychain": true,
		"verbose": true
        ] as OAuth2JSON)
    
        let sessionManager = SessionManager()
        let retrier = OAuth2RetryHandler(oauth2: oauth2)
	sessionManager.retrier 	= retrier
	sessionManager.adapter	= retrier
	alamofireManager = sessionManager
		
	oauth2.delegate = self
    }
    
    public func authorize(view: UIViewController, completion: @escaping ( OAuth2JSON? ) -> Void) {		
	oauth2.authConfig.authorizeEmbedded = true
	oauth2.authConfig.authorizeContext = view

        alamofireManager.request("https://api.github.com/user").validate().responseJSON { (response) in
	    debugPrint(response)
	    if let dict = response.result.value as? [String: Any] {
	       print(dict)
	    }
	    else {
	       print(OAuth2Error.generic("\(response)"))
	    }
	}
    }

    public func loginController(oauth2: OAuth2PasswordGrant) -> AnyObject {
        if let vc = oauth2.authConfig.authorizeContext as? UIViewController {
		vc.present(LoginViewController(), animated: true, completion: nil)
		return vc
	}
	return LoginViewController()
    }
}

For this build I get the following error:

[Debug] OAuth2: Initialization finished
[Debug] OAuth2: Looking for items in keychain
[Debug] OAuth2: Initialization finished
[Debug] OAuth2: Starting authorization
[Debug] OAuth2: No access token, checking if a refresh token is available
[Debug] OAuth2: Error refreshing token: I don't have a refresh token, not trying to refresh
[Debug] OAuth2: Presenting the login controller
2018-03-01 13:56:54.497130+0100 LibAccount_Example[424:81789] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Application tried to present modal view controller on itself. Presenting controller is <LibAccount_Example.ViewController: 0x102216aa0>.'

ItsMeSandu avatar Mar 01 '18 12:03 ItsMeSandu

My current solution looks like the following:

My 4 components:

  • BasicViewController
  • CustomLoginViewController
  • OAuth2RetryHandler
  • LoginHandler

BasicViewController:

    class BasicViewController: UIViewController {
        var loginHandler: LoginHandler?

        override func viewDidLoad() {
            super.viewDidLoad()

            // Do any additional setup after loading the view.
            loginHandler = LoginHandler(clientId: OAUTH_CLIENT_ID, clientSecret: OAUTH_CLIENT_SECRET)
        }
	
        override func viewDidAppear(_ animated: Bool) {
          super.viewDidAppear(animated)
          let userLoggedIn = UserDefaults.standard.bool(forKey: "LOGGED_IN")
		
          let token = loginHandler?.oauth2.accessToken

          if (!userLoggedIn) || (userLoggedIn && token == nil) {
          	loginHandler?.authorize(presenting: self)
          }
        }
	
        public func logout() {
          loginHandler?.logout()
          self.viewDidAppear(true)
        }

        @objc internal func handleLogoutButton(sender: UIButton) {
  	     logout()
        }
    }

    extension BasicViewController: OAuth2PasswordGrantDelegate {
        public func loginController(oauth2: OAuth2PasswordGrant) -> AnyObject {
        	return CustomLoginViewController(oauth: oauth2)
        }
    }

CustomLoginViewController:

    open class CustomLoginViewController: UIViewController {
        // MARK: - object-properties
        fileprivate var oauth: OAuth2PasswordGrant?

        // MARK: - system-methods
        public init(oauth: OAuth2PasswordGrant) {
          self.oauth = oauth
        	super.init(nibName: nil, bundle: nil)
        }

        required public init?(coder aDecoder: NSCoder) {
        	super.init(coder: aDecoder)
        }

        override open func viewDidLoad() {
          super.viewDidLoad()

          // Do any additional setup after loading the view.
        }
    }

OAuth2RetryHandler: is just copied from the sample

LoginHandler:

    open class LoginHandler {
      public var oauth2: OAuth2PasswordGrant
      open var alamofireManager: SessionManager

      public init(clientId: String, clientSecret: String) {
      	
        oauth2 = OAuth2PasswordGrant(settings: [
        	"client_id": clientId,
        	"client_secret": clientSecret,
        	"token_uri": TOKEN_URI,
        	"keychain": true,
        	"grant_type": "password",
        	"scope": "",
        	"verbose": true
        ] as OAuth2JSON)
      	
        let sessionManager = SessionManager()
        let retrier = OAuth2RetryHandler(oauth2: oauth2)
        sessionManager.retrier 	= retrier
        sessionManager.adapter	= retrier
        alamofireManager = sessionManager
      }

      public func authorize(presenting view: UIViewController) {
        // as far as I know now, the following if-request is not necessary
        // because "authorizeEmbedded" is checking it already
        if oauth2.isAuthorizing {
        	oauth2.abortAuthorization()
        	return
        }

        if let view = view as? OAuth2PasswordGrantDelegate {
        	oauth2.delegate = view
        }

        oauth2.authorizeEmbedded(from: view) { (authParameters, error) in
        	if let _ = authParameters {
        		print(authParameters)
        		UserDefaults.standard.set(true, forKey: "LOGGED_IN")
        	}
        	else {
        		UserDefaults.standard.set(false, forKey: "LOGGED_IN")
        		print("Authorization was canceled or went wrong: \(String(describing: error?.description))")
        	}
        }
      }

      public func logout() {
        oauth2.forgetTokens()
        oauth2.username	= nil
        oauth2.password	= nil
        UserDefaults.standard.set(false, forKey: "LOGGED_IN")
      }
    }

ItsMeSandu avatar Mar 15 '18 07:03 ItsMeSandu

@PetresS - Did you manage to solve this issue?

TheSwiftyCoder avatar Apr 22 '18 19:04 TheSwiftyCoder

@TheSwiftyCoder yes I did. The 2nd comment is my current solution, but I'm not sure if my solution is a "best practice"-solution.

I would appreciate if someone could post a best-practice-sample.

ItsMeSandu avatar Apr 23 '18 07:04 ItsMeSandu

@PetresS

Your solution gave me north, but I think I'm doing something wrong because my login screen did not render :(

I don't know what can be.

AndersonAltissimo avatar Jun 23 '18 16:06 AndersonAltissimo

@AndersonAltissimo

did you solve your problem? sorry for late response :(

ItsMeSandu avatar Aug 08 '18 08:08 ItsMeSandu

@PetresS

No problem man I solved the problem 👍

AndersonAltissimo avatar Aug 08 '18 14:08 AndersonAltissimo

Struggling with the same, the documentation could need a fair share of updating here. There's no description of the flow of how you are supposed to work with the delagate and perform the actual login.

grEvenX avatar Mar 20 '19 22:03 grEvenX