amplify-cli: Allowing multiple redirectSignIn/redirectSignOut urls breaks federated auth

Describe the bug Using the Amplify CLI to add multiple redirectSignIn/redirectSignOut URLs results in a broken config

To Reproduce Steps to reproduce the behavior:

  1. Use amplify add auth to set up a federated auth config
  2. Add a redirectSignIn url, e.g. http://localhost:3000
  3. Add an additional redirectSignIn url, e.g. https://d32tfey1ge36f1.cloudfront.net/
  4. Add the same for redirectSignOut urls
  5. See the config has the two urls joined by a comma, e.g. "redirectSignIn": "http://localhost:3000/,https://d32tfey1ge36f1.cloudfront.net/",
  6. Attempt to authenticate in the app and see the error due to the comma-separated urls not being a valid format for the OAuth provider

Expected behavior The CLI should not allow multiple URLs to be entered if that results in a non-working config

Additional context Using Google as the OAuth provider, the following invalid URI is generated due to the multiple comma-separated urls being set as the redirect_uri value:

https://example-dev.auth.us-west-2.amazoncognito.com/oauth2/authorize?redirect_uri=http%3A%2F%2Flocalhost%3A3000%2F%2Chttps%3A%2F%2Fd32tfey1ge36f1.cloudfront.net%2F&response_type=code&client_id=23r4j2asdfelvhasdfas8md4t&identity_provider=Google&scopes=phone%2Cemail%2Copenid%2Cprofile%2Caws.cognito.signin.user.admin&state=ymIeVUG2ez8pbasdfasdfWiG1u42Frn&code_challenge=asdfasdfasdfb7aLeB8FAQv_V7P9TTyZzGasdfasffj0Us&code_challenge_method=S256

Notice the redirect_uri parameter has both URLs.

I solved this problem in my react.js app by overriding the awsmobile config something like this:

import awsmobile from './aws-exports';

const { NODE_ENV } = process.env;
const DEFAULT_URL = 'http://localhost:3000/';

if (NODE_ENV === 'development') {
  awsmobile.oauth.redirectSignIn = DEFAULT_URL;
  awsmobile.oauth.redirectSignOut = DEFAULT_URL;
}
export default awsmobile;

About this issue

  • Original URL
  • State: open
  • Created 5 years ago
  • Reactions: 34
  • Comments: 17 (4 by maintainers)

Commits related to this issue

Most upvoted comments

I’d like to first comment that I’m really impressed with this library, and I’d like to give a hearty thank you to the teams that have added so many useful, well-integrated, and multi-faceted features

I disagree that this is “normal behavior”. The CLI has the capability to add multiple redirectSignIn/redirectSignOut values to the config, but it adds them in such a way that breaks the functionality of the app.

The code you posted does serve as a workaround to this issue, but it is not really satisfactory, because you’re hard-coding values in the code that overwrite the generated values in the config. This defeats the purpose of using the CLI and the config generation capabilities.

In my opinion, supporting the multiple redirect urls, and detecting when to use which is an implementation detail that should be abstracted away by the library.

I did the similar workaround with the above and it worked. I’m sharing the code as this could be one of the smallest code example -

import Amplify from "aws-amplify";
import awsconfig from "./aws-exports";

awsconfig.oauth.redirectSignIn = `${window.location.origin}/`;
awsconfig.oauth.redirectSignOut = `${window.location.origin}/`;

Amplify.configure(awsconfig);

Note that this works only when your redirectSignIn and redirectSignOut is ${window.location.origin}/.

Sorry guys but in fact, this is a normal behavior.

Setup amplify add auth with two URLs: “http://localhost:3000/,https://master.xxx.amplifyapp.com/” to add the sign in/sign out URLs, amplify push then git commit & git push to make the amplify console pick up the changes.

Then you get a redirect_mismatch error locally and online https://master.xxx.amplifyapp.com/

Why ? There is no way for the react app. to know by default which URLs to use when you have two or more URLs. You must inform the app. to use one of these URLs. You can do it like that:

What can you do ? After looking in the doc., you find pretty much a solution here: https://aws-amplify.github.io/docs/js/authentication#react-components

import Amplify, { Auth } from 'aws-amplify'
import config from './aws-exports'

and,

// copied from serviceWorker.js to know if it is localhost or not
const isLocalhost = Boolean(
  window.location.hostname === 'localhost' ||
    // [::1] is the IPv6 localhost address.
    window.location.hostname === '[::1]' ||
    // 127.0.0.1/8 is considered localhost for IPv4.
    window.location.hostname.match(
      /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
    )
);

// by default, say it's localhost
const oauth = {
  domain: 'xxx.auth.us-east-2.amazoncognito.com',
  scope: ['phone', 'email', 'profile', 'openid', 'aws.cognito.signin.user.admin'],
  redirectSignIn: 'http://localhost:3000/',
  redirectSignOut: 'http://localhost:3000/',
  responseType: 'code' // or 'token', note that REFRESH token will only be generated when the responseType is code
};

// if not, update the URLs
if (!isLocalhost) {
  oauth.redirectSignIn = 'https://master.xxx.amplifyapp.com/';
  oauth.redirectSignOut = 'https://master.xxx.amplifyapp.com/';
}

// copy the constant config (aws-exports.js) because config is read only.
var configUpdate = config;
// update the configUpdate constant with the good URLs
configUpdate.oauth = oauth;
// Configure Amplify with configUpdate
Amplify.configure(configUpdate);

Full code & example You can find a demo here that also work in localhost: https://master.d3h5j4begww46c.amplifyapp.com/ And a github fork (from dabit3): https://github.com/arelaxtest/amplify-auth-demo

Next steps More generally, you can do something like:

var urlsIn = config.oauth.redirectSignIn.split(",");
var urlsOut = config.oauth.redirectSignOut.split(",");
const oauth = {
  domain: config.oauth.domain,
  scope: config.oauth.scope,
  redirectSignIn: config.oauth.redirectSignIn,
  redirectSignOut: config.oauth.redirectSignOut,
  responseType: config.oauth.responseType
};
var hasLocalhost  = (hostname) => Boolean(hostname.match(/localhost/) || hostname.match(/127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}/));
var hasHostname   = (hostname) => Boolean(hostname.includes(window.location.hostname));
var isLocalhost   = hasLocalhost(window.location.hostname);
if (isLocalhost) {
  urlsIn.forEach((e) =>   { if (hasLocalhost(e)) { oauth.redirectSignIn = e; }});
  urlsOut.forEach((e) =>  { if (hasLocalhost(e)) { oauth.redirectSignOut = e; }});
}
else {
  urlsIn.forEach((e) =>   { if (hasHostname(e)) { oauth.redirectSignIn = e; }});
  urlsOut.forEach((e) =>  { if (hasHostname(e)) { oauth.redirectSignOut = e; }});
}
var configUpdate = config;
configUpdate.oauth = oauth;
Amplify.configure(configUpdate);

+1, this feature seems important and needed, please prioritize!!!

+1 to @djheru . In addition, Amplify allows multiple environments, for each of which there is a separate cognito user pool created. The whole point of aws-exports.js generated during build time was for it to figure out what parameters to configure Amplify with. If we cannot figure out what redirectURI to use per environment during build time, that kinda defeats the purpose of this aws-exports.js being generated.

For someone who cannot access to window object:

function findUrlForEnv(urlStrings: Array<string>, isLocal: boolean): string {
  if (urlStrings.length === 1) return urlStrings[0];

  const re: RegExp = isLocal ? /^http:\/\/localhost/ : /^https:\/\//;
  const [url]: Array<URL> = urlStrings
    .filter((urlString) => urlString.match(re))
    .map((urlString) => new URL(urlString));
  if (!url) throw new Error("No valid URL found: " + urlStrings.join(","));
  return url.href;
}

function isDevelopment() {
  const { NODE_ENV } = process.env;
  return NODE_ENV === "development";
}

const redirectSignIn = findUrlForEnv(
    awsconfig.oauth.redirectSignIn.split(","),
    isDevelopment()
  );

I ended up with a similar solution, but it works just fine

import awsmobile from './aws-exports';

const { host } = window.location;

// Fix issues with multiple redirect urls.
// Try to figure out which one to use...
if (awsmobile.oauth.redirectSignIn.includes(',')) {
  const filterHost = url => new URL(url).host === host;
  awsmobile.oauth.redirectSignIn = awsmobile.oauth.redirectSignIn
    .split(',')
    .filter(filterHost)
    .shift();
  awsmobile.oauth.redirectSignOut = awsmobile.oauth.redirectSignOut
    .split(',')
    .filter(filterHost)
    .shift();
}
export default awsmobile;

I can also confirm that having multiple redirect URLs in your awsconfiguration.json will break federated auth. Additionally, amplify auth update does not give you the option to remove it, in case you’ve added it by accident. The only way to remove the extra URL (in my case if was not needed) is to manually modify amplify-meta.json and possibly parameters.json and then do another amplify push. I hope this helps somebody, it took me hours to get my federated login to work again.

I think that the most dynamic solution would be to have named/aliased redirects that are stored in amplify config and added to the aws-exports file. This would allow for normalization across multiple environments and also enable selection of which redirect path to use.

For me, my main challenge is always to use localhost vs the actual hosted webapp. That would look something like:

// config for dev amplify environment
redirectUris: {
  localhost: localhost:8080,
  localhostDashboard: localhost:8080/dashboard,
  awsHosted: dev.example.org,
  awsHostedDashboard: dev.example.org/dashboard
}
// config for prod amplify environment
redirectUris: {
  localhost: localhost:8080,
  localhostDashboard: localhost:8080/dashboard,
  awsHosted: www.example.org,
  awsHostedDashboard: dev.example.org/dashboard
}
// Amplify javascript call
Auth.federatedSignIn({ redirect: 'awsHosted' })

This would allow us to whitelist all of those various domains in the Cognito Hosted UI and social providers but also select them conveniently in the frontend code without duplicate sources of truth.

I also noticed while typing this that the aws-exports file does not include an environment name (eg. env: ‘dev’). This seems like a missing feature that would simplify this context management n the frontend when Amplify doesn’t do so automatically.

Just for new Amplify users like me who come across this issue, they just want to add a production URI to the login redirect. You should just use one URI per environment:

In command line, do:

amplify env add

to add a production environment and configure the new environment in amplify studio, change the redirect uri to production uri.

You can switch env:

amplify env checkout production

before you build the production version of code.

My federated config works locally but doesn’t work in prod in a Gatsby project. I am pretty sure it’s related to the switching of the redirect URI’s

It sounds more like amplify publish should update it’s aws-exports.js before uploading to point to the URL based on aws_content_delivery_url in same file - and that npm start should use the aws-exports.js localhost always (i.e. not updating the file).

That seems much simpler than our code dealing with it or hard coding - Am I missing something? (New to Amplify).