Hello, I am trying to implement federated signIn using AppSync. I create everything as described in documentation, but for some unknown reasons I still can't send requests with app sync.
I create stack using snippet:
let cacheConfiguration = try? AWSAppSyncCacheConfiguration()
let appSyncServiceConfig = try? AWSAppSyncServiceConfig()
let appSyncConfig = try? AWSAppSyncClientConfiguration(
appSyncServiceConfig: AWSAppSyncServiceConfig(),
userPoolsAuthProvider: MyCognitoUserPoolsAuthProvider(),
cacheConfiguration: cacheConfiguration)
appSyncClient = try? AWSAppSyncClient(appSyncConfig: appSyncConfig!)
using standard implementation of MyCognitoUserPoolsAuthProvider:
class MyCognitoUserPoolsAuthProvider: AWSCognitoUserPoolsAuthProviderAsync {
func getLatestAuthToken(_ callback: @escaping (String?, Error?) -> Void) {
AWSMobileClient.sharedInstance().getTokens { (tokens, error) in
if error != nil {
callback(nil, error)
} else {
callback(tokens?.idToken?.tokenString, nil)
}
}
}
}
Next, I register: (result.token and result.identityId are provided by my backend)
AWSMobileClient.sharedInstance().federatedSignIn(
providerName: IdentityProvider.developer.rawValue,
token: result.token,
federatedSignInOptions: FederatedSignInOptions(cognitoIdentityId: result.identityId)) { (userState, error) in
if let error = error as? AWSMobileClientError {
print(error.localizedDescription)
}
if let userState = userState {
print("Status: \(userState.rawValue)")
}
}
which results with userState == signedIn. Every app sync request is finishing with unauthorized error. I've created breakpoint inside MyCognitoUserPoolsAuthProvider, where I saw that AWSMobileClient.sharedInstance().getTokens always results with empty token and error. I've also tried to use alternative implementation of this class. It is returning token created by my backend, but I also get unauthorized error:
class MyCognitoUserPoolsAuthProvider: AWSCognitoUserPoolsAuthProviderAsync {
func getLatestAuthToken(_ callback: @escaping (String?, Error?) -> Void) {
AWSMobileClient.sharedInstance().logins().continueWith { (result) -> Any? in
callback(result.result?["cognito-identity.amazonaws.com"] as? String, nil)
return nil
}
}
}
I currently have no idea what I'm doing wrong and how can I fix my problem. Can anybody help me solve it?
Which AWS Services are you utilizing?
AWSMobileClient, AWSAppSync
Environment(please complete the following information):
Device Information (please complete the following information):
@bananita
Sorry for the trouble you're having with this--unfortunately, Auth is a very confusing issue at the best of times and Cognito is a pretty complicated system.
tl;dr: If you use Cognito User Pools and Hosted UI, everything should "just work" for your use case. Otherwise, you can use a custom UI and invoke showSignIn with an identityProvider in your HostedUIOptions.
It sounds like you're using UserPools as your primary Authorization method for your AppSync installation, based on the fact that your code snippet shows you configuring with userPoolsAuthProvider: MyCognitoUserPoolsAuthProvider.
In addition, you're using the federatedSignIn API to try to Authenticate into your UserPools, using your own custom UI. I believe you're making the reasonable assumption that if one of your users signs into your UserPool via, for example, Facebook, then you'll be able to use UserPools to authenticate the user for your AppSync API.
Unfortunately, that's not quite how Cognito User Pools works with federation. You'll have to use a different approach.
In the interest of laying some groundwork for potential future documentation updates, I'm going to go into more detail than is strictly warranted by your question, and perhaps cover some ground you're already familiar with.
Step 1 is to authenticate the user--that is, provide some assurance that a given user is who they say they are, (for example, based on their ability to give a username and password). Cognito User Pools is one way to do this: it allows you to set up a user store--a directory of users and passwords that represent known users in your system.
Other ways exist: for example, you could use Facebook to vouch for a user via Federated sign in. At the end of that process, Cognito would know that Facebook recognizes the user. However, neither the AWSMobileClient drop in UI, nor the AWSMobileClient.signIn method, will create a User Pool record for the user with this method.
This last piece is critical. Currently, the only way to get a User Pool record from a Federated Sign In flow is to use Cognito's Hosted UI. HostedUI has the necessary setups in place to talk to Federated providers to set up a User Pool record. The AWSMobileClient.federatedSignIn method is only used to federate into Cognito Identity Pools (see below), not for User Pools. And neither the no-option flavor of AWSMobileClient.showSignIn() (which displays our drop-in UI) nor the AWSMobileClient.signIn method create a User Pools user.
What you want is to use HostedUI, by invoking AWSMobileClient.showSignIn(hostedUIOptions:). Despite the name, using HostedUI does give you some flexibility to use your own UI, as we'll discuss below.
AppSync requires some kind of credentials to authorize that a user has permissions to access your API. The four authorization modes AppSync recognizes are:
Each of these methods have some prerequisites:
awsconfiguration.json file. However, it is also the least secure, and only suitable for unauthenticated APIs.Your best option is to use AWSMobileClient.showSignIn(navigationController:signInUIOptions:hostedUIOptions:_:), and pass a HostedUIOptions object to control the behavior. You can do this one of two ways:
This is the easiest, as it uses Cognito's HostedUI web page to provide a secure login experience for your users.
let hostedUIOptions = HostedUIOptions(
/* Configure as appropriate, but do not specify `identityProvider` */
)
// Present the Hosted UI sign in.
AWSMobileClient.default().showSignIn(navigationController: navigationController) { userState, error in
if let error = error as? AWSMobileClientError {
print(error.localizedDescription)
return
}
guard let userState = userState else {
print("userState unexpectedly nil")
return
}
print(userState)
}
As described here
@IBOutlet @objc func didTapSignInWithFacebook(){
let hostedUIOptions = HostedUIOptions(scopes: ["openid", "email"], identityProvider: "Facebook")
AWSMobileClient.default().showSignIn(navigationController: self.navigationController!,
hostedUIOptions: hostedUIOptions) { userState, error in
if let error = error as? AWSMobileClientError {
print(error.localizedDescription)
return
}
guard let userState = userState else {
print("userState unexpectedly nil")
return
}
print(userState)
}
}
Finally, I'm tagging @kneekey23 and @undefobj to clarify/correct anything I've missed above.
Hope this helps.
This issue has been automatically closed because of inactivity. Please open a new issue if are still encountering problems.
Most helpful comment
@bananita
Sorry for the trouble you're having with this--unfortunately, Auth is a very confusing issue at the best of times and Cognito is a pretty complicated system.
tl;dr: If you use Cognito User Pools and Hosted UI, everything should "just work" for your use case. Otherwise, you can use a custom UI and invoke
showSignInwith anidentityProviderin yourHostedUIOptions.Discussion
It sounds like you're using UserPools as your primary Authorization method for your AppSync installation, based on the fact that your code snippet shows you configuring with
userPoolsAuthProvider: MyCognitoUserPoolsAuthProvider.In addition, you're using the
federatedSignInAPI to try to Authenticate into your UserPools, using your own custom UI. I believe you're making the reasonable assumption that if one of your users signs into your UserPool via, for example, Facebook, then you'll be able to use UserPools to authenticate the user for your AppSync API.Unfortunately, that's not quite how Cognito User Pools works with federation. You'll have to use a different approach.
In the interest of laying some groundwork for potential future documentation updates, I'm going to go into more detail than is strictly warranted by your question, and perhaps cover some ground you're already familiar with.
Part 1: Authentication
Step 1 is to authenticate the user--that is, provide some assurance that a given user is who they say they are, (for example, based on their ability to give a username and password). Cognito User Pools is one way to do this: it allows you to set up a user store--a directory of users and passwords that represent known users in your system.
Other ways exist: for example, you could use Facebook to vouch for a user via Federated sign in. At the end of that process, Cognito would know that Facebook recognizes the user. However, neither the AWSMobileClient drop in UI, nor the
AWSMobileClient.signInmethod, will create a User Pool record for the user with this method.This last piece is critical. Currently, the only way to get a User Pool record from a Federated Sign In flow is to use Cognito's Hosted UI. HostedUI has the necessary setups in place to talk to Federated providers to set up a User Pool record. The
AWSMobileClient.federatedSignInmethod is only used to federate into Cognito Identity Pools (see below), not for User Pools. And neither the no-option flavor ofAWSMobileClient.showSignIn()(which displays our drop-in UI) nor theAWSMobileClient.signInmethod create a User Pools user.What you want is to use HostedUI, by invoking
AWSMobileClient.showSignIn(hostedUIOptions:). Despite the name, using HostedUI does give you some flexibility to use your own UI, as we'll discuss below.Part 2: Authorization
AppSync requires some kind of credentials to authorize that a user has permissions to access your API. The four authorization modes AppSync recognizes are:
Each of these methods have some prerequisites:
awsconfiguration.jsonfile. However, it is also the least secure, and only suitable for unauthenticated APIs.Options for Federating into User Pools
Your best option is to use
AWSMobileClient.showSignIn(navigationController:signInUIOptions:hostedUIOptions:_:), and pass aHostedUIOptionsobject to control the behavior. You can do this one of two ways:Option 1: Full Hosted UI experience
This is the easiest, as it uses Cognito's HostedUI web page to provide a secure login experience for your users.
Option 2: Custom UI using Hosted UI for federation
As described here
Finally, I'm tagging @kneekey23 and @undefobj to clarify/correct anything I've missed above.
Hope this helps.