I implemented MSAL-based Enterprise SSO in my application, but I ran into a problem with token renewal. To verify a request to the API, I use idToken, which I receive after authorization. Its lifetime is approximately 1 hour. For login, I use the call
func signIn(completion: @escaping (Result<MSALResult, Error>) -> Void) {
guard let applicationContext = applicationContext else { return }
guard let rootVC = UIApplication.shared.windows.first?.rootViewController else {
completion(.failure(NSError(domain: "MSAL", code: -2, userInfo: [NSLocalizedDescriptionKey: "No rootViewController"])))
return
}
let webViewParameters = MSALWebviewParameters(authPresentationViewController: rootVC)
let parameters = MSALInteractiveTokenParameters(scopes: ["user.read"], webviewParameters: webViewParameters)
applicationContext.acquireToken(with: parameters) { result, error in
if let error = error {
completion(.failure(error))
return
}
guard let result = result else {
completion(.failure(NSError(domain: "MSAL", code: -1, userInfo: [NSLocalizedDescriptionKey: "No result"])))
return
}
self.isLoggedWithMSAL = true
if let identifier = result.account.identifier {
CommonService.setMSALAccountIdentifier(identifier: identifier)
}
completion(.success(result))
}
}
and this does not cause problems but later I need to update this token (for example, 5 minutes before expiration and when I received an error from the api that my token is invalid) - I call
func getMSALAuthToken(completion: @escaping (String?) -> Void) {
guard let applicationContext = applicationContext else {
SGuardLog.e("MSAL context is not initialized")
completion(nil)
return
}
let parameters = MSALParameters()
parameters.completionBlockQueue = DispatchQueue.main
applicationContext.getCurrentAccount(with: parameters) { currentAccount, _, error in
if let error = error {
SGuardLog.e("Failed to get accounts: \(error)")
completion(nil)
return
}
guard let currentAccount = currentAccount, currentAccount.identifier == CommonService.getMSALAccountIdentifier() else {
SGuardLog.e("No matching account found")
completion(nil)
return
}
let silentParams = MSALSilentTokenParameters(scopes: ["user.read"], account: currentAccount)
applicationContext.acquireTokenSilent(with: silentParams) { result, error in
if let result = result {
completion(result.idToken)
} else {
SGuardLog.e("Silent token refresh failed: \(error?.localizedDescription ?? "Unknown error")")
completion(nil)
}
}
}
}
but in this case I get the same expired token (I parsed it in jwt io and checked it). Most likely, the problem is in the scopes I am using. I found that the offline_access
scope is required to renew the token
I granted permission for it in the azure config, added it as parameter scope, but as a result I can't even log in and get the error The operation couldn’t be completed. (MSALErrorDomain error -50000.)
What specific scopes and permissions need to be configured to be able to renew the token for SSO?
The behaviour you are seeing is expected. Calling acquireTokenSilent
will return the current tokens, if the accessToken
has not expired. This is to prevent unnecessary, expensive, token refresh transactions.
In your case you have an issue, because the idToken
, which you need, has expired, but the accessToken
has not.
You can set forceRefresh
on the MSALSilentTokenParameters
to true
to request new tokens when the access token has not expired.
You will need to check the exp
claim of your idToken
and when it is getting close to expiration, request a new set of tokens with forceRefresh=true