aws-amplify / amplify-js

A declarative JavaScript library for application development using cloud services.
https://docs.amplify.aws/lib/q/platform/js
Apache License 2.0
9.42k stars 2.12k forks source link

Feature Request: Support for multiple Auth user pools #661

Closed kerly closed 3 years ago

kerly commented 6 years ago

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).

simonbuchan commented 6 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.

39ro commented 6 years ago

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;

What I did:

  1. Form of login asks for $username, $password, $user_pool_id
  2. Change the configuration of Amplify to reflect the new UserPoolId.
  3. Store the UserPoolId in a service to let my users continue the navigation in the website.
  4. Sign In => Login succesfully, it works but...

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?

  1. Is User Pool Id a sensiteve data? No, they are not. They are supposed to be public.
PerfectPixel commented 6 years ago

@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?

jbham commented 5 years ago

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.

adam-harwood commented 5 years ago

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:

jbham commented 5 years ago

Thanks for the reply @adam-harwood! I'll take a look into it.

larryranches commented 5 years ago

+1 on this. Would be nice to have support for this out of the box with Amplify.

CoonHouse commented 5 years ago

@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 commented 5 years ago

@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:

simonbuchan commented 5 years ago

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.

CoonHouse commented 5 years ago

@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 commented 5 years ago

@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.

simonbuchan commented 5 years ago

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.

CoonHouse commented 5 years ago

@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

sammartinez commented 5 years ago

@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!

simonbuchan commented 5 years ago

@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.

frostini commented 4 years ago

@simonbuchan thanks for sharing your experience with this, learned from your comments and will keep them in mind as I work toward a solution!

frostini commented 4 years ago

btw just found related issue and merged PR 1916

sakhmedbayev commented 4 years ago

I wonder if it is accomplishable using Cognito User Pool Lambda Triggers?

Did someone come up with the better solution that we have above?

KIWI-JP commented 3 years ago

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?

harrysolovay commented 3 years ago

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!

github-actions[bot] commented 2 years ago

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.