I'm using AppAuth pod to handle user login with Azure in my app. I followed this sample :
https://github.com/openid/AppAuth-iOS/tree/master/Examples which works fine until my authentication code expires. It works ok for the 1st connection and all the time while the authenticationCode is still valid. Once it expires, I briefly see the alert to "Sign in" and then it disappears and I get the error :"Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior". (It works fine again if I delete the app and re-install it.)
I read that I should "Ensure that there is a strong reference to the SFAuthenticationSession instance when the session is in progress.". And I think that's the case with the currentFlow declared in AppDelegate. (see code below) Did anyone ever faced and solved this issue ?import UIKit
import AppAuth
import AuthenticationServices
var isLoginViewOn: Bool = false
var isConnectionBtnPressed: Bool = false
class ContainerController: UIViewController {
// MARKS : Properties
private var authState: OIDAuthState?
var token: String?
var menuController: MenuController!
var homeController: HomeController!
var panView: UIView!
var isExpanded = false
var isLoginOut: Bool = false
let loginView: UIImageView = {
let v = UIImageView()
v.image = UIImage(named: "")
v.contentMode = .scaleAspectFit
return v
}()
let connexionButton: UIButton = {
let b = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
return b
}()
let logoutBtn: UIButton = {
let b = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
b.setTitle("déconnexion", for: .normal)
return b
}()
override func viewDidLoad() {
super.viewDidLoad()
chekToken()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if isLoginViewOn == true {
if isConnectionBtnPressed == false {
self.connexionButton.sendActions(for: .touchUpInside)
isLoginViewOn = false
}
}
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
view.layoutIfNeeded()
view.layoutSubviews()
}
/////////////////////////////////////////////////////////////////////////////
/// SET UP ////
/////////////////////////////////////////////////////////////////////////////
func setupLoginView() {
print("setup loginView")
isLoginViewOn = true
view.addSubview(loginView)
view.addSubview(connexionButton)
loginView.translatesAutoresizingMaskIntoConstraints = false
connexionButton.translatesAutoresizingMaskIntoConstraints = false
[
loginView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
loginView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
loginView.widthAnchor.constraint(equalToConstant: 250),
loginView.heightAnchor.constraint(equalToConstant: 128),
connexionButton.bottomAnchor.constraint(equalTo: loginView.topAnchor, constant: -50),
connexionButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
connexionButton.widthAnchor.constraint(equalToConstant: 200),
connexionButton.heightAnchor.constraint(equalToConstant: 50),
].forEach{$0.isActive = true }
connexionButton.addTarget(self, action: #selector(buttonAction(_:)), for: .touchUpInside)
if isConnectionBtnPressed == false {
self.connexionButton.sendActions(for: .touchUpInside)
}
}
func setupHomeController() {
homeController = HomeController()
homeController.delegate = self
addControllerAsChild(forController: homeController)
setupPanView(forController: homeController)
}
}The login part :extension ContainerController {
@objc func buttonAction(_ sender: UIButton){
self.login()
isConnectionBtnPressed = true
}
func checkToken() {
guard let data = UserDefaults.standard.object(forKey: kAppAuthExampleAuthStateKey) as? Data else {
setupLoginView()
return
}
do {
let authState = try NSKeyedUnarchiver.unarchivedObject(ofClass: OIDAuthState.self, from: data)
self.setAuthState(authState)
self.getUserInfo()
} catch { print("catch loadState: \(error)") }
}
func login() {
print("Got configuration: \(config)")
if let clientId = kClientID {
self.doAuthWithAutoCodeExchange(configuration: config, clientID: clientId, clientSecret: nil)
}
}
func doAuthWithAutoCodeExchange(configuration: OIDServiceConfiguration, clientID: String, clientSecret: String?) {
guard let redirectURI = URL(string: kRedirectURI) else {
print("Error creating URL for : \(kRedirectURI)")
return
}
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
print("Error accessing AppDelegate")
return
}
// builds authentication request
let request = OIDAuthorizationRequest(configuration: configuration,
clientId: clientID,
clientSecret: clientSecret,
scopes: [OIDScopeOpenID, OIDScopeProfile, "--kclientID--", "offline_access"],
redirectURL: redirectURI,
responseType: OIDResponseTypeCode,
additionalParameters: ["p": "b2c_1_my_app_sign_in_up"])
// performs authentication request
print("Initiating authorization request with scope: \(request.scope ?? "DEFAULT_SCOPE")")
appDelegate.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, presenting: self) { authState, error in
if let authState = authState {
self.setAuthState(authState)
print("Got authorization tokens. Access token")
self.getUserInfo()
} else {
self.setAuthState(nil)
print("Authorization error: \(error?.localizedDescription ?? "DEFAULT_ERROR")")
}
}
}
func getUserInfo() {
let currentAccessToken: String? = self.authState?.lastTokenResponse?.accessToken
self.authState?.performAction() { (accessToken, idToken, error) in
if error != nil {
CoreDataHelper().deleteIDToken("idToken")
AuthenticationService().login()
print("Error fetching fresh tokens: \(error?.localizedDescription ?? "ERROR")")
return
}
guard let accessToken = accessToken else {
print("Error getting accessToken")
return
}
if currentAccessToken != accessToken {
print("Access token was refreshed automatically ")
self.token = currentAccessToken
} else {
print("Access token was fresh and not updated ")
self.token = accessToken
}
self.loginView.removeFromSuperview()
self.setupHomeController()
}
}
func saveState() {
var data: Data? = nil
if let authState = self.authState {
do {
data = try NSKeyedArchiver.archivedData(withRootObject: authState, requiringSecureCoding: false)
} catch { print("catch saveState: \(error)") }
}
UserDefaults.standard.set(data, forKey: kAppAuthExampleAuthStateKey)
UserDefaults.standard.synchronize()
}
func setAuthState(_ authState: OIDAuthState?) {
if (self.authState == authState) {
return;
}
self.authState = authState;
self.authState?.stateChangeDelegate = self;
self.saveState()
}
}
extension ContainerController: ASWebAuthenticationPresentationContextProviding {
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
return view.window!
}And AppDelegate above the class declaration :let kClientID: String? = "my client id";
let kRedirectURI: String = "myapp.test.authent://oauth/redirect";
let kAppAuthExampleAuthStateKey: String = "authState";
let authorizationEndpoint = URL(string: "https://login.microsoftonline.com/myapp.onmicrosoft.com/oauth2/v2.0/authorize?p=b2c_1_myaapp_sign_in_up")!
let tokenEndpoint = URL(string: "https://login.microsoftonline.com/myapp.onmicrosoft.com/oauth2/v2.0/token?p=b2c_1_myaapp_sign_in_up")!
let config = OIDServiceConfiguration(authorizationEndpoint: authorizationEndpoint,
tokenEndpoint: tokenEndpoint)And in the class : var currentAuthorizationFlow: OIDExternalUserAgentSession?
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
// Sends the URL to the current authorization flow (if any) which will
// process it if it relates to an authorization response.
if let authorizationFlow = self.currentAuthorizationFlow, authorizationFlow.resumeExternalUserAgentFlow(with: url) {
self.currentAuthorizationFlow = nil
return true
}
return false
}Any suggestion welcomed 🙂