diff --git a/docs/media/authentication_guide.md b/docs/media/authentication_guide.md index b327584723f..4659d733c92 100644 --- a/docs/media/authentication_guide.md +++ b/docs/media/authentication_guide.md @@ -292,7 +292,7 @@ this._validAuthStates = ['signedIn']; in the component's constructor, then implement `showComponent(theme) {}` in lieu of the typical `render() {}` method. -### Federated Identities (Social Sign-in) +### Using Federated Identities (Social Sign-in) **Availibility Note** Currently, our federated identity components only support Google, Facebook and Amazon identities, and works with React. @@ -352,6 +352,125 @@ const federated = { There is also `withGoogle`, `withFacebook`, `withAmazon` components, in case you need to customize a single provider. + + +### Using Amazon Cognito Hosted UI + +Amazon Cognito provides a customizable user experience via the hosted UI. The hosted UI supports OAuth 2.0 and Federated Identities with Facebook, Amazon, Google, and SAML providers. + +Note: Amazon Cognito hosted UI feature is supported with *aws-amplify@^0.2.15* and *aws-amplify-react@^0.1.39* versions. +{: .callout .callout--info} + +#### Setup your Cognito App Client + +To start using hosted UI, first, you need to setup your App Client in the Amazon Cognito console. + +To setup App Client; +- Go to [Amazon Cognito Console](https://aws.amazon.com/cognito/). +- Click *User Pools* on the top menu to select a User Pool or create a new one. +- Click *App integration* and *App client settings* on the left menu. +- Select *Enabled Identity Providers* and enter *Callback URL(s)* and *Sign out URL(s)* fields. +- Under the *OAuth 2.0* section, select an OAuth Flow. *Authorization code grant* is the recommended choice for security reasons. +- Choose item(s) from *OAuth Scopes*. +- Click 'Save Changes' + +To enable the domain for your hosted UI; + +- On the left menu, go to *App integration* > *Domain name*. +- In the *Domain prefix* section, enter the prefix for the pages that will be hosted by Amazon Cognito. + +You can also enable Federated Identities for your hosted UI; + +- Go to *Federation* > *Identity providers* +- Select an *Identity provider* and enter required credentials for the identity provider. (e.g., App Id, App secret, Authorized scope) +- In the settings page for your selected identity provider (Facebook, Google, etc.), set *Oauth Redirected URI* to `https://your-domain-prefix.auth.us-east 1.amazoncognito.com/oauth2/idpresponse` (*your-domain-prefix* is the domain prefix you have entered in previously). +- To retrieve user attributes from your identity provider, go to *Federation* > *Attribute mapping*. Here, you can map Federation Provider attributes to corresponding User pool attributes. + +If *email* attribute is a required field in your Cognito User Pool settings, please make sure that you have selected *email* in your Authorized Scopes, and you have mapped it correctly to your User Pool attributes. +{: .callout .callout-info} + +#### Configuring the Hosted UI + +To configure your application for hosted UI, you need to use *oauth* options: + +```js +import Amplify from 'aws-amplify'; + +const oauth = { + // Domain name + domain : 'your-domain-prefix.auth.us-east-1.amazoncognito.com', + + // Authorized scopes + scope : ['phone', 'email', 'profile', 'openid','aws.cognito.signin.user.admin'], + + // Callback URL + redirectSignIn : 'http://www.example.com/signin', + + // Sign out URL + redirectSignOut : 'http://www.example.com/signout', + + // 'code' for Authorization code grant, + // 'token' for Implicit grant + responseType: 'code' + + // optional, for Cognito hosted ui specified options + options: { + // Indicates if the data collection is enabled to support Cognito advanced security features. By default, this flag is set to true. + AdvancedSecurityDataCollectionFlag : true + } +} + +Amplify.configure({ + Auth: { + // other configurations... + // .... + oauth: oauth + }, + // ... +}); +``` + +#### Launching the Hosted UI + +To invoke the browser to display the hosted UI, you need to construct the URL in your app; + +```js +const config = Auth.configure(); +const { + domain, + redirectSignIn, + redirectSignOut, + responseType } = config.oauth; + +const clientId = config.userPoolWebClientId; +const url = 'https://' + domain + '/login?redirect_uri=' + redirectSignIn + '&response_type=' + responseType + '&client_id=' + clientId; + +// Launch hosted UI +window.location.assign(url); + +``` + +#### Launching the Hosted UI in React + +With React, you can simply use `withOAuth` HOC to launch the hosted UI experience. Just wrap your app's main component with our HOC: + +```js +import { withOAuth } from 'aws-amplify-react'; + +class MyApp extends React.Component { + // ... + render() { + return( + + ) + } +} + +export default withOAuth(MyApp); +``` + ### Enabling MFA (Multi-Factor Authentication) Multi-factor authentication (MFA) increases security for your app by adding an authentication method and not relying solely on username (or alias) and password. AWS Amplify uses Amazon Cognito to provide MFA. Please see [Amazon Cognito Developer Guide](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-mfa.html) for more information about setting up MFA in Amazon Cognito. diff --git a/packages/aws-amplify-react/__tests__/Auth/ConfirmSignIn-test.js b/packages/aws-amplify-react/__tests__/Auth/ConfirmSignIn-test.js index 32c613a723f..844eeb66ea0 100644 --- a/packages/aws-amplify-react/__tests__/Auth/ConfirmSignIn-test.js +++ b/packages/aws-amplify-react/__tests__/Auth/ConfirmSignIn-test.js @@ -21,7 +21,7 @@ const deniedStates = [ ]; describe('ConfirmSignIn', () => { - describe('normal case', () => { + describe('render test', () => { test('render correctly with Props confirmSignIn', () => { const wrapper = shallow(); for (var i = 0; i < acceptedStates.length; i += 1){ @@ -33,6 +33,59 @@ describe('ConfirmSignIn', () => { } }); + test('render corrently with other authstate', () => { + const wrapper = shallow(); + + for (var i = 0; i < deniedStates.length; i += 1){ + wrapper.setProps({ + authState: deniedStates[i], + theme: AmplifyTheme + }); + + expect(wrapper).toMatchSnapshot(); + } + }); + + test('hidden if hide include confirmSignIn', () => { + const wrapper = shallow(); + wrapper.setProps({ + authState: acceptedStates[0], + hide: [ConfirmSignIn] + }); + expect(wrapper).toMatchSnapshot(); + }); + + }); + + describe('confirm test', () => { + test('user with challengeName SOFTWARE_TOKEN_MFA', async () => { + const wrapper = shallow(); + + const spyon = jest.spyOn(Auth, 'confirmSignIn').mockImplementationOnce(() => { + return Promise.resolve(); + }); + + wrapper.setProps({ + authState: acceptedStates[0], + theme: AmplifyTheme, + authData: { + user: { + challengeName: 'SOFTWARE_TOKEN_MFA' + } + } + }); + + const confirmSignIn = wrapper.instance(); + + await confirmSignIn.confirm(); + + expect(spyon).toBeCalled(); + + spyon.mockClear(); + }); + }); + + describe('normal case', () => { test('simulate clicking confirm button', async () => { const spyon = jest.spyOn(Auth, 'confirmSignIn') .mockImplementation((user, code) => { @@ -83,19 +136,4 @@ describe('ConfirmSignIn', () => { spyon2.mockClear(); }); }); - - describe('null case with other authState', () => { - test('render corrently', () => { - const wrapper = shallow(); - - for (var i = 0; i < deniedStates.length; i += 1){ - wrapper.setProps({ - authState: deniedStates[i], - theme: AmplifyTheme - }); - - expect(wrapper).toMatchSnapshot(); - } - }); - }); }) diff --git a/packages/aws-amplify-react/__tests__/Auth/FederatedSignIn-test.js b/packages/aws-amplify-react/__tests__/Auth/FederatedSignIn-test.js index 35c38bd0a81..de50d98afb5 100644 --- a/packages/aws-amplify-react/__tests__/Auth/FederatedSignIn-test.js +++ b/packages/aws-amplify-react/__tests__/Auth/FederatedSignIn-test.js @@ -1,5 +1,12 @@ import React from 'react'; import FederatedSignIn, { FederatedButtons } from '../../src/Auth/FederatedSignIn'; +import { Auth } from 'aws-amplify'; + +const spyon = jest.spyOn(Auth, 'configure').mockImplementation(() => { + return { + hostedUIOptions: {} + } +}) describe('FederatedSignIn test', () => { describe('render test', () => { @@ -7,7 +14,7 @@ describe('FederatedSignIn test', () => { const wrapper = shallow(); wrapper.setProps({ - federated: true, + federated: {}, authState: 'signIn', onStateChange: jest.fn() }); @@ -18,7 +25,7 @@ describe('FederatedSignIn test', () => { const wrapper = shallow(); wrapper.setProps({ - federated: true, + federated: {}, authState: 'signedIn', onStateChange: jest.fn() }); @@ -29,7 +36,7 @@ describe('FederatedSignIn test', () => { const wrapper = shallow(); wrapper.setProps({ - federated: false, + federated: undefined, authState: 'signIn', onStateChange: jest.fn() }); diff --git a/packages/aws-amplify-react/__tests__/Auth/ForgotPassword-test.js b/packages/aws-amplify-react/__tests__/Auth/ForgotPassword-test.js index 628b9d1b3ad..25870091efc 100644 --- a/packages/aws-amplify-react/__tests__/Auth/ForgotPassword-test.js +++ b/packages/aws-amplify-react/__tests__/Auth/ForgotPassword-test.js @@ -46,6 +46,15 @@ describe('forgotPassword', () => { } }); + test('hidden if hide include ForgotPassword', () => { + const wrapper = shallow(); + wrapper.setProps({ + authState: acceptedStates[0], + hide: [ForgotPassword] + }); + expect(wrapper).toMatchSnapshot(); + }); + test('simulating clicking submit', async () => { const spyon = jest.spyOn(Auth, 'forgotPasswordSubmit') .mockImplementationOnce(() => { diff --git a/packages/aws-amplify-react/__tests__/Auth/Provider/__snapshots__/withOAuth-test.js.snap b/packages/aws-amplify-react/__tests__/Auth/Provider/__snapshots__/withOAuth-test.js.snap new file mode 100644 index 00000000000..a9f530ef8e1 --- /dev/null +++ b/packages/aws-amplify-react/__tests__/Auth/Provider/__snapshots__/withOAuth-test.js.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`withOAuth test render test render correctly 1`] = ` + +`; + +exports[`withOAuth test render test render correctly with button 1`] = ` +