Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow AWSMobileClient to accept tokens as a starting point #5447

Closed
ghost opened this issue Oct 1, 2024 · 6 comments
Closed

Allow AWSMobileClient to accept tokens as a starting point #5447

ghost opened this issue Oct 1, 2024 · 6 comments
Labels
auth feature-request Request a new feature

Comments

@ghost
Copy link

ghost commented Oct 1, 2024

Which AWS Services is the feature request for?

AWSMobileClient Cognito.

Is your feature request related to a problem? Please describe.

Using Sign in with Apple as an identity provider is problematic, because unlike other providers, Apple does not provide a client-side method for refreshing tokens and Cognito does not handle it for developers either. See this issue for more details.

To work around this, I've attempted to implement a hybrid approach:

  1. The user Signs in with Apple in the client.
  2. The client sends the Apple tokens to the AWS server.
  3. The AWS server validates the tokens, admin-creates a Cognito User Pool user, confirms the user, and generates Cognito tokens for the user.

This is where the problem arises.

First, the tokens generated server-side are not universal in the sense that they aren't automatically shared with the client. Meaning, if the client then attempts AWSMobileClient.default().getTokens(), it will throw an error saying the user is not signed in.

Second, if the server returned the tokens to the client, they cannot be passed to AWSMobileClient.

This means the only solution is likely to not use AWSMobileClient at all, and manually handle all client-side logic related to signing in, signing out, getting tokens, etc.

Describe the solution you'd like

Add a feature like AWSMobileClient.default().setTokens(idToken, accessToken, refreshToken).

This will make it so the server can generate the initial tokens, after which AWSMobileClient will handle the process as usual.

AWSMobileClient does not need to do anything regarding Sign in with Apple. It simply needs to accept the Cognito tokens as a starting point, after which its flow remains unchanged.

The client is responsible for detecting if or when Sign in with Apple authorization is revoked, at which point will also revoke the Cognito tokens and require the user to begin the process again.

Describe alternatives you've considered

There are very few alternatives to using Sign in with Apple with Cognito, other than doing everything yourself or switching to another provider like Firebase, which does handle refreshing Apple's tokens for the developer.

Additional context

Sign in with Apple is mandatory in all applications that provide any other identity-provider sign in options. Cognito and AWSMobileClient do not provide much support for Sign in with Apple other than the bare-bones minimum which is not entirely workable in most cases.

@github-actions github-actions bot added pending-triage Issue is pending triage pending-maintainer-response Issue is pending response from an Amplify team member labels Oct 1, 2024
@harsh62 harsh62 added feature-request Request a new feature auth labels Oct 1, 2024
@harsh62
Copy link
Member

harsh62 commented Oct 1, 2024

@occassionally I have marked this as a feature request.

You could try using HostedUI which generates and returns the token to AWSMobileClient.

@github-actions github-actions bot removed the pending-maintainer-response Issue is pending response from an Amplify team member label Oct 1, 2024
@ghost
Copy link
Author

ghost commented Oct 1, 2024

@harsh62 Thanks! Unfortunately, using HostedUI is not really an option due to its appearance. Even with styling options, it's not what most users expect from a production iOS app. Can the token-refreshing logic of HostedUI be replicated in AWSMobileClient? It seems like if HostedUI can do it, AWSMobileClient could too.

@github-actions github-actions bot added the pending-maintainer-response Issue is pending response from an Amplify team member label Oct 1, 2024
@harsh62
Copy link
Member

harsh62 commented Oct 1, 2024

token-refreshing logic of HostedUI be replicated in AWSMobileClient

Unfortunately that is not true because HostedUI uses Cognito's internal API to achieve that logic which is not available to AWS SDK. AWSMobileClient is only able to access publicly released Cognito APIs. This has been requested a few times on Github and we forwarded that request to Cognito service team. I would suggest you to open a ticket with AWS support.

For the time being, accepting tokens in not available. You could build it yourself and open a PR. I will be happy to look at it.

@github-actions github-actions bot removed the pending-maintainer-response Issue is pending response from an Amplify team member label Oct 1, 2024
@ghost
Copy link
Author

ghost commented Oct 4, 2024

Understood, thanks for the clarification.

For the time being, accepting tokens in not available. You could build it yourself and open a PR. I will be happy to look at it.

That might be an option. I'm not too familiar with this library though, so could you please let me know if I'm on the right track with this?

It looks like getTokens()uses FetchUserPoolTokensOperation:

public func getTokens(_ completionHandler: @escaping TokenCompletion) {
switch self.federationProvider {
case .userPools:
AWSMobileClientLogging.verbose("Adding FetchUserPoolTokensOperation operation")
let operation = FetchUserPoolTokensOperation(completion: completionHandler)
operation.delegate = self
tokenOperations.add(operation)
tokenFetchOperationQueue.addOperation(operation)
case .hostedUI:
AWSMobileClientLogging.verbose("Invoking hostedUI getTokens")
let operation = FetchUserPoolTokensOperation(
userPool: AWSCognitoAuth(forKey: AWSMobileClientConstants.CognitoAuthRegistrationKey),
completion: completionHandler)
operation.delegate = self
tokenOperations.add(operation)
tokenFetchOperationQueue.addOperation(operation)
default:
let message = AWSMobileClientConstants.notSignedInMessage
let error = AWSMobileClientError.notSignedIn(message: message)
completionHandler(nil, error)
}
}
}

Which uses its internal fetchToken() function:

private func fetchToken() {
AWSMobileClientLogging.verbose("\(self.identifier) Inside fetch token")
guard
!self.isCancelled
else {
AWSMobileClientLogging.verbose("\(self.identifier) Cancelled")
finish()
return
}
guard
let username = self.delegate?.getCurrentUsername(operation: self),
!username.isEmpty
else {
AWSMobileClientLogging.verbose("\(self.identifier) No User name")
self.acceptEvent(.noUserFound)
return
}
let user = self.userPool.getIdentityUser(username)
user.getUserPoolToken { [weak self] result in
guard let self = self else { return }
guard
!self.isCancelled
else {
AWSMobileClientLogging.verbose("\(self.identifier) Cancelled")
self.finish()
return
}
switch result {
case .success(let tokens):
self.acceptEvent(.tokenFetched(tokens))
case .failure(let error):
if (error as NSError).didTokenExpire {
AWSMobileClientLogging.verbose("\(self.identifier) Need re-authentication")
self.acceptEvent(.tokenExpired)
} else {
self.acceptEvent(.serviceError(error))
}
}
}
}

Specifically, this would be the end result of a successful setTokens() invocation:

getTokens() handles two cases: .userPools and .hostedUI, otherwise it throws an error. setTokens() would handle only .userPools. It would accept a Tokens object:

public struct Tokens {
/// The ID token.
public let idToken: SessionToken?
/// The access token.
public let accessToken: SessionToken?
/// The refresh token.
public let refreshToken: SessionToken?
/// Expiration date if available.
public let expiration: Date?
}

And if the federationProvider is .userPools, it will call acceptEvent(.tokenFetched(tokens)).

If that all sounds about right, could you please give me some clarity on these questions:

  1. Since getTokens() is using the Cognito API, it knows the tokens are valid if the case is .success. setTokens() would be accepting a Token object that might not have valid tokens. Is there any need for setTokens() to do some kind of validation, and if so, are there built-in functions to do that? I imagine the simplest validation would be when the existing infrastructure of AWSMobileClient attempts to use the tokens and finds that they're invalid, right? But that may not be acceptable. If it isn't, and validation is necessary, hopefully there's some kind of logic already implemented in the library that could be used.
  2. After AWSMobileClient is provided the initial tokens via setTokens(), will it handle the token refresh process going forward from that point, as if it had retrieved the tokens itself via getTokens()? Or would the developer need to manually handle it and keep calling setTokens() periodically? I hope the answer to this is that it will behave normally, until the user signs out; so overall, setTokens() would only be necessary on each sign in.

@github-actions github-actions bot added the pending-maintainer-response Issue is pending response from an Amplify team member label Oct 4, 2024
@mattcreaser mattcreaser removed the pending-triage Issue is pending triage label Oct 4, 2024
@thisisabhash thisisabhash removed the pending-maintainer-response Issue is pending response from an Amplify team member label Oct 11, 2024
@ghost
Copy link
Author

ghost commented Oct 15, 2024

Closed since maintainers will not respond.

@ghost ghost closed this as completed Oct 15, 2024
Copy link

This issue is now closed. Comments on closed issues are hard for our team to see.
If you need more assistance, please open a new issue that references this one.
If you wish to keep having a conversation with other community members under this issue feel free to do so.

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
auth feature-request Request a new feature
Projects
None yet
Development

No branches or pull requests

3 participants