aws-amplify/amplify-swift

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.