Closed kerly closed 3 years ago
Tooling doesn't support it well, but this is possible already to an extent:
// The auth-specific values from 'aws-exports.json' for your user pools, can be hand-written:
// https://aws.github.io/aws-amplify/media/authentication_guide#manual-setup
import authOptionsAlpha from './auth-options-alpha.json';
import authOptionsBeta from './auth-options-beta.json';
async function suffixLogin(username, password) {
// Whatever works for you:
const authOptions = username.endsWith('@alpha.com') ? authOptionsAlpha : authOptionsBeta;
// Note, this is the value of the `Auth` key in `aws-exports`, not the top-level.
Amplify.Auth.configure(authOptions);
const user = await Amplify.Auth.signIn(username, password);
// rest of standard login flow
}
Or you could detect if login returns "username not found" and reconfigure with the next, etc., don't have tested code for that.
We're doing something like this for a developer mode that lets us at runtime switch the backends for the same build of a react-native app for testing purposes.
Actually, I achieved a scenario to allow multiple user pool in a single application, where a User is able to choose the User Pool during the login.
I used an Angular (<3) application to replicate this scenario.
My basic aws-export.ts:
const awsmobile = {
Auth: {
identityPoolId: '**-****-*:********',
region: '**-****-*',
userPoolWebClientId: '************************'
}
};
export default awsmobile;
If I try to refresh the application my UserPoolId variable will be cleaned and my application broke.
Right, here I spent few hours to figure out a possible solution and here what come out:
During the signIn: localStorage.setItem('amplify-user-pool-id', selectedUserPoolId)
During the signOut: localStorage.removeItem('amplify-user-pool-id')
In my main.ts (where the app bootstrap) check if in my localStorage is present a userPoolId, if it is, load it if not load my default awsmobile configuration!
import awsmobile from './aws-exports';
...
if (!!localStorage.getItem('amplify-user-pool-id')) {
Auth.configure(
{
Auth: {
identityPoolId: '**-****-*:********',
region: '**-****-*',
userPoolWebClientId: '************************',
userPoolId: localStorage.getItem('amplify-user-pool-id')
}
});
} else {
Auth.configure(awsmobile);
}
...
Possible PR: Could be a solution adding in the Auth flow of Amplify to store your UserPoolId in the localStorage using the key amplify-user-pool-id?
@39ro
short question regarding your suggestion: what about the userPoolWebClientId
? Isn't this specific to the pool or do you solve this with having an identity pool?
Hello!
Any chance this is being considered as a feature request or has this already been solved? Trying to implement multi user pool solution in my app as well.
Hello!
Any chance this is being considered as a feature request or has this already been solved? Trying to implement multi user pool solution in my app as well.
There is a decent workaround available today, described by @simonbuchan above:
Amplify.configure
with that info.Thanks for the reply @adam-harwood! I'll take a look into it.
+1 on this. Would be nice to have support for this out of the box with Amplify.
@adam-harwood and /or @simonbuchan , Is there some documentation, tutorial or sample project that follows this approach?
I would like to implement it this way, but I am lost. I find myself constantly starting over without the desired result.
Thanks, Wilco
@adam-harwood and /or @simonbuchan , Is there some documentation, tutorial or sample project that follows this approach?
I would like to implement it this way, but I am lost. I find myself constantly starting over without the desired result.
Thanks, Wilco
Let me try. So assuming you have a React component that shows your login form (username field, password field, "sign-in" button). When the user clicks sign-in, it would invoke this super-simplified-but-instructional method:
import Amplify, { Auth } from 'aws-amplify';
//...
signIn = (username, password) => {
//Before doing the actual sign-in, configure the Auth module
if (username.endsWith("@test.domain")) {
Amplify.configure({
Auth: {
region: 'ap-southeast-2',
userPoolId: 'ap-southeast-2_XXXXXXX',
userPoolWebClientId: 'abc1234567890',
}
});
}
//add configuration for each tenant you have
else if (...) {
}
//Now that the Auth module is configured, do the actual login
Auth.signIn({ username, password })
.then(() => {
//Successful login, update state to render your application
})
.catch(err => {
//login failed, possibly a bad password
});
};
A few important notes about the above code:
then
function above, and would need to be handled specifically. Hopefully this gets you started though.Also, of course, you could simply show a list of pools (e.g. workspaces, organizations, etc...) on the login screen, or hit an endpoint that has a table of userid to pool info (though that would make public that the account exists and what org they are in) or whatever other system you have.
It is ugly, and has the problems @adam-harwood points out, but from testing there doesn't seem to be a better option right now. In theory, creating new instances of AuthClass
should solve that, but when I tested that a while ago, there was a lot of calling back and forward between Auth
and Credentials
(IIRC) that use the globals, so it doesn't actually work. If those references were fixed to properly isolate custom Auth
instances, then that would make this problem a lot cleaner.
@adam-harwood and @simonbuchan , Thanks for your replies.
@adam-harwood, what I ment was, how to place this in an self created HOC. Sorry I had to be more precisely. And when going with this approach, I cannot use "amplify push" anymore, i guess?
@simonbuchan do you think this is bad practice? Would you advise another way to implement multi-tenant functionality?
Thanks, Wilco
@adam-harwood, what I ment was, how to place this in an self created HOC. Sorry I had to be more precisely. And when going with this approach, I cannot use "amplify push" anymore, i guess?
I think you might find it easiest to build your own HOC, rather than re-use Amplify's Authenticator in any way. A component that checks if the user is logged-in, in componentDidMount (assuming React), and if so displays the app or if not display the Sign-in form should be all you need.
I don't have much experience with amplify push (I manage all of my resources with cloud formation running from a CodePipeline). I'm guessing it assumes there's only 1 cognito pool though, so I could see that being a deal breaker. The code itself would be fine though, just maybe manage your authentication pools in their own cloudformation stack.
do you think this is bad practice? Would you advise another way to implement multi-tenant functionality?
Specifically using email suffixes, hard coded into the app? Probably fine for getting off the ground, but you'll probably want something fancier pretty quick, whether that's a token in a sign-up link, db lookup, etc.. Do be careful to do security threat modelling and make sure you're not leaking info though.
Where "this" is using your business logic to determine which pool to log into? Perfectly fine, if that's what you actually need for your app. If this is simply multi-tenanting, then you should be aware there's a soft limit of 1000 user pools, so if you're likely to blow past that, then you might want to do what we're looking into now, which is switch to a single user pool and using attributes to determine tenant. The trade off is that you can't directly use IAM to enforce resource access per tenant any more, but we're looking into options, the most promising looking being providing a "tenant resource endpoint" that uses STS to vend credentials with per-tenant access using session policies. Be warned this is not at all drop-in: you will need to be pretty confident messing around with the AWS permissions system, but it seems like the cleanest and most performant option.
@adam-harwood and @simonbuchan , Thanks, @adam-harwood: I will try to create my own HOC for authentication.
@simonbuchan: I will investigate which route to take. Using multiple user pools or using some sort of "tenantid". I want to provide each customer (tenant) the possibility to create users and assign them specific rights to selected data inside their own environment (tenant). So my guess would be that using multiple user pools would be easier.
Regards
Wilco
@kerly @simonbuchan can you speak more to the use cases that you have in regards to this. I am trying to understand why not using IAM polices and defining roles for specific users would not suffice for this use case. For soft limits on how many users there can be in a user pools, you are allowed 20M users.
From a performance perspective, it seems that it would add time to loop through each user pool to find a specific user which could lead to the application possibly acting inactive. There is also the cost of having multiple user pools with the same users.
Just wanted a little be more information. Thanks!
@sammartinez Depends. We originally went with separate user and identity pools per tenant as we wanted to let tenants set their password policy differently in theory (though I don't think it ended up being a thing), it is much easier to enforce tenant data separation by using identity pool ids, since they're available in IAM condition variables and lambda context, and because the triggers are easier to customize on a per-tenant basis. For production usage, we always knew which pool a login should be for, as we were building SPAs with that information embedded, but for admin tools it would be nice to be able to handle multiple Auth instances simultaneously. Going forward, we want to be able to have users belong to multiple tenants, and we're likely to have more than 1000 user pools with our current design too, so we're working through moving to a single shared pool by default and building custom logic to handle tenanting (e.g. something like a DynamoDB membership table).
AFAICT, you need to use groups to assign separate roles to users, and they have a hard limit of 500, so you can't really use them as tenants.
@simonbuchan thanks for sharing your experience with this, learned from your comments and will keep them in mind as I work toward a solution!
I wonder if it is accomplishable using Cognito User Pool Lambda Triggers?
Did someone come up with the better solution that we have above?
I followed this approach and was able to successfully switch between user pools. However, and as expected, users signed in using the new user pool I added manually to my project (I already had one which was setup with my amplify project) are not able to access any of the restricted APIs I had originally created. How can I give this new user pool access to the same APIs or new ones?
While this is an interesting use case, it isn't very common practice and could cause issues, as the locally-persisted auth state will be confused by the regular pool switching. This is not something we're soon likely to address.
If you'd like to submit a PR, we're more than happy to review!
This issue has been automatically locked since there hasn't been any recent activity after it was closed. Please open a new issue for related bugs.
Looking for a help forum? We recommend joining the Amplify Community Discord server *-help
channels or Discussions for those types of questions.
I would like to request that the Auth class have support for multiple user pools similar to the way the API class has support for multiple endpoints. Currently I am unable (as far as I am aware) to configure multiple user pools in the same application. If there is already a solution for this please point me in the direction of the documentation as I was not able to find it.
Use cases for this feature: The ability to allow users to sign into the same website from different user pools (different user pools are used to differentiate users such as what the user can do).