401 Unauthorized Errors Due to missing token in Amplify iOS SDK (Version 2.33.6)
Opened this issue · 13 comments
Describe the bug
We are experiencing intermittent 401 Unauthorized errors in our iOS app using AWS Amplify SDK version 2.33.6. The issue seems to be affecting some users but not all, and we have been unable to replicate the problem internally. The errors appear to be related to missing or invalid tokens, with most server logs indicating missing tokens as the primary cause.
Steps To Reproduce
1. Implement authentication using AWS Amplify and Cognito in an iOS app.
2. Use the fetchAuthSession() method to get tokens which will be use as bearer token for api endpoints.
3. After some time, observe that API requests result in 401 errors due to expired tokens.
Additional note:
One user reported that he/she encountered this issue right after updating the myVicroads app to the latest version. However, the user also said he/she hadn't logged in for a while before updating the app.
Expected behavior
The fetchAuthSession() method should automatically refresh expired tokens or provide valid tokens, preventing 401 errors due to missing or invalid tokens.
Amplify Framework Version
2.33.6
Amplify Categories
Auth
Dependency manager
Swift PM
Swift version
5
CLI version
2
Xcode version
16.1
Relevant log output
<details>
<summary>Log Messages</summary>
Invalid tokens
2024-11-28T00:41:51.994Z b6d1358e-928f-4e93-bd74-d432af989c25 trce [Information] Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler: Bearer was not authenticated. Failure message: IDX10223: Lifetime validation failed. The token is expired. ValidTo: 'System.DateTime', Current time: 'System.DateTime'.
2024-11-28T00:42:16.660Z b4e2bc2f-f221-47a9-95b7-364a8b2e949f trce [Information] Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler: Failed to validate the token.
Missing tokens
2024-11-28T01:28:45.573Z 2f3cd8e2-e043-4032-82d2-7442cc63335e trce [Information] Microsoft.AspNetCore.Authorization.DefaultAuthorizationService: Authorization failed. These requirements were not met:
DenyAnonymousAuthorizationRequirement: Requires an authenticated user.
Is this a regression?
Yes
Regression additional context
No response
Platforms
iOS
OS Version
18.1, 17.4
Device
iPhone 16, iPhone 12
Specific to simulators
No response
Additional context
- I have reviewed the changes in version 2.39.0 (#3827), which include fixes related to authentication and session management.
- The problem might be addressed by these updates, but I am seeking confirmation before upgrading.
- Attached is a snippet of my AmplifyAuthManager class implementation for reference.
public actor AmplifyAuthManager: AuthManagerProtocol, TokenProvider {
let auth: AuthCategory
public static let manager = AmplifyAuthManager()
private init() {
do {
try Amplify.add(plugin: AWSCognitoAuthPlugin())
try Amplify.configure()
} catch {
print("Failed to initialize Amplify with \(error)")
}
auth = Amplify.Auth
#if DEBUG
Amplify.Logging.logLevel = .verbose
#endif
}
public func signOut() async -> Result<AuthStatus, GenericAPIError> {
do {
guard try await auth.fetchAuthSession().isSignedIn else {
return .success(.signedOut)
}
let signOutState = await auth.signOut()
guard let signOutResult = signOutState as? AWSCognitoSignOutResult else {
return .failure(.other("Sign out failed"))
}
switch signOutResult {
case .complete:
return .success(.signedOut)
case .partial(revokeTokenError: let revokeTokenError,
globalSignOutError: _,
hostedUIError: _):
return .failure(.other(String(describing: revokeTokenError)))
case .failed(let error):
return .failure(.authError(error.toLoginError()))
}
} catch {
debugPrint("AmplifyAuthManager signOut failed")
debugPrint(error.localizedDescription)
return .failure(.other(""))
}
}
func fetchAuthSession() async -> Result<AuthStatus, GenericAPIError> {
do {
let authSession = try await auth.fetchAuthSession()
let tokens = try getTokens(authSession as? AuthCognitoTokensProvider)
if authSession.isSignedIn, let _ = tokens {
return .success(.signedIn)
} else {
return await signOut()
}
} catch let authError as AuthError {
return .failure(.authError(authError.toLoginError()))
} catch {
return .failure(.someThingWentWrong(error))
}
}
private func getTokens(_ tokenProvider: AuthCognitoTokensProvider?) throws -> AuthCognitoTokens? {
try tokenProvider?.getCognitoTokens().get()
}
}
However, the user also said he/she hadn't logged in for a while before updating the app
How long do you think a while is? Is it possible the refresh token expired? How long are refresh token's set to be valid on your account? Once a refresh token is expired, the user will have to logout and back in.
I do not believe the referenced issue you pointed to would be related.
@tylerjroach we set the validity of the refresh token to 10 years and the access token to 15 minutes. Our app has only been live for more than a year, so we expect the user to remain logged in even after they update the app.
@jerfranco-deloitte Some of the cases where a refresh token could be invalidated are:
- You're using a different app client ID than the one that issued the refresh token
- Your device tracking is turned on in Cognito User Pools
I have a few other questions:
- Can you share you configuration file redacted all the sensitive information? Also are you using
clientSecret
? - Did it start happening suddenly? Did something change recently?
- Is it happening for certain type of users? Users logging in via email or phone?
One user reported that he/she encountered this issue right after updating the myVicroads app to the latest version. However, the user also said he/she hadn't logged in for a while before updating the app.
Did this also include an update the Amplify library?
Any other info you could provide that could narrow down the investigation?
@jerfranco-deloitte Can you try a small change and see if that works?
func fetchAuthSession() async -> Result<AuthStatus, GenericAPIError> {
do {
let authSession = try await auth.fetchAuthSession()
let tokens = try getTokens(authSession as? AuthCognitoTokensProvider)
if authSession.isSignedIn, let _ = tokens {
return .success(.signedIn)
} else {
return await signOut()
}
} catch let authError as AuthError {
return .failure(.authError(authError.toLoginError()))
} catch {
return .failure(.someThingWentWrong(error))
}
}
Just change your above code to something as below and see if that resolved the issue?
func fetchAuthSession() async -> Result<AuthStatus, GenericAPIError> {
do {
let authSession = try await auth.fetchAuthSession()
if authSession.isSignedIn {
let tokens = try getTokens(authSession as? AuthCognitoTokensProvider)
if let _ = tokens {
return .success(.signedIn)
} else {
return await signOut()
}
} else {
return await signOut()
}
} catch let authError as AuthError {
return .failure(.authError(authError.toLoginError()))
} catch {
return .failure(.someThingWentWrong(error))
}
}
Looking forward to your feedback, thanks.
@harish-suthar, here are the answers to your questions:
- You're using a different app client ID than the one that issued the refresh token
We're using the same app client ID
- Your device tracking is turned on in Cognito User Pools
Device tracking is turned off in Cognito User Pools
- Can you share you configuration file redacted all the sensitive information? Also are you using
clientSecret
?
Here's my configuration file:
{
"UserAgent": "aws-amplify-cli/2.0",
"Version": "1.0",
"auth": {
"plugins": {
"awsCognitoAuthPlugin": {
"UserAgent": "aws-amplify-cli/0.1.0",
"Version": "0.1.0",
"IdentityManager": {
"Default": {}
},
"CredentialsProvider": {
"CognitoIdentity": {
"Default": {
"PoolId": "ap-southeast-2:REDACTED",
"Region": "ap-southeast-2"
}
}
},
"CognitoUserPool": {
"Default": {
"PoolId": "ap-southeast-2_REDACTED",
"AppClientId": "REDACTED",
"Region": "ap-southeast-2",
}
}
}
}
}
}
- Did it start happening suddenly? Did something change recently?
This issue started to happen after we upgraded to 2.33.6
- Is it happening for certain type of users? Users logging in via email or phone?
Users can log into the app via email and password, but we still can't determine which type of users are encountering this issue.
@jerfranco-deloitte Are you somehow able to grab the verbose logs when this issue happens?
@harsh62 How do you access amplify verbose logs?
You can enable verbose logging to the console by doing this before calling Amplify.configure
:
Amplify.Logging.logLevel = .verbose
You can enable verbose logging to the console by doing this before calling
Amplify.configure
:Amplify.Logging.logLevel = .verbose
@harsh62 I mean is there any way I can send these verbose logs to a logging service, to see the production logs as this issue is not reproducible easily and we are getting reports of this from production app for some users.
You will need to setup the logging category to send the logs to AWSCloudWatch. The setup guide is available here: https://docs.amplify.aws/swift/build-a-backend/add-aws-services/logging/set-up-logging/.
You will need to setup the logging category to send the logs to AWSCloudWatch. The setup guide is available here: https://docs.amplify.aws/swift/build-a-backend/add-aws-services/logging/set-up-logging/.
@harish-suthar @harsh62 Thanks for the advice. This might help since we can't easily reproduce the production issue in our non-prod environments. @harsh62, do we need to set Amplify.Logging.logLevel = .verbose
for this logging setup?
@jerfranco-deloitte No need to do it like this there is config file in which you can specify check below link for the same.
https://docs.amplify.aws/gen1/swift/build-a-backend/more-features/logging/flush-logs/
@jerfranco-deloitte Sorry for losing track of the issue. Yes verbose logs would be the best at detecting what really happened to the app.