Skip to content

Commit

Permalink
Merge pull request #585 from powerful23/cognito-hosted-ui
Browse files Browse the repository at this point in the history
Cognito hosted ui
  • Loading branch information
powerful23 authored Apr 13, 2018
2 parents 2270af2 + f3e65aa commit 8eeef2f
Show file tree
Hide file tree
Showing 23 changed files with 1,578 additions and 121 deletions.
121 changes: 120 additions & 1 deletion docs/media/authentication_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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(
<button onClick={this.props.OAuthSignIn}>
Sign in with AWS
</button>
)
}
}

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.
Expand Down
70 changes: 54 additions & 16 deletions packages/aws-amplify-react/__tests__/Auth/ConfirmSignIn-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const deniedStates = [
];

describe('ConfirmSignIn', () => {
describe('normal case', () => {
describe('render test', () => {
test('render correctly with Props confirmSignIn', () => {
const wrapper = shallow(<ConfirmSignIn/>);
for (var i = 0; i < acceptedStates.length; i += 1){
Expand All @@ -33,6 +33,59 @@ describe('ConfirmSignIn', () => {
}
});

test('render corrently with other authstate', () => {
const wrapper = shallow(<ConfirmSignIn/>);

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(<ConfirmSignIn/>);
wrapper.setProps({
authState: acceptedStates[0],
hide: [ConfirmSignIn]
});
expect(wrapper).toMatchSnapshot();
});

});

describe('confirm test', () => {
test('user with challengeName SOFTWARE_TOKEN_MFA', async () => {
const wrapper = shallow(<ConfirmSignIn/>);

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) => {
Expand Down Expand Up @@ -83,19 +136,4 @@ describe('ConfirmSignIn', () => {
spyon2.mockClear();
});
});

describe('null case with other authState', () => {
test('render corrently', () => {
const wrapper = shallow(<ConfirmSignIn/>);

for (var i = 0; i < deniedStates.length; i += 1){
wrapper.setProps({
authState: deniedStates[i],
theme: AmplifyTheme
});

expect(wrapper).toMatchSnapshot();
}
});
});
})
13 changes: 10 additions & 3 deletions packages/aws-amplify-react/__tests__/Auth/FederatedSignIn-test.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
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', () => {
test('render with correct authState', () => {
const wrapper = shallow(<FederatedSignIn/>);

wrapper.setProps({
federated: true,
federated: {},
authState: 'signIn',
onStateChange: jest.fn()
});
Expand All @@ -18,7 +25,7 @@ describe('FederatedSignIn test', () => {
const wrapper = shallow(<FederatedSignIn/>);

wrapper.setProps({
federated: true,
federated: {},
authState: 'signedIn',
onStateChange: jest.fn()
});
Expand All @@ -29,7 +36,7 @@ describe('FederatedSignIn test', () => {
const wrapper = shallow(<FederatedSignIn/>);

wrapper.setProps({
federated: false,
federated: undefined,
authState: 'signIn',
onStateChange: jest.fn()
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ describe('forgotPassword', () => {
}
});

test('hidden if hide include ForgotPassword', () => {
const wrapper = shallow(<ForgotPassword/>);
wrapper.setProps({
authState: acceptedStates[0],
hide: [ForgotPassword]
});
expect(wrapper).toMatchSnapshot();
});

test('simulating clicking submit', async () => {
const spyon = jest.spyOn(Auth, 'forgotPasswordSubmit')
.mockImplementationOnce(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`withOAuth test render test render correctly 1`] = `
<MockComp
OAuthSignIn={[Function]}
/>
`;

exports[`withOAuth test render test render correctly with button 1`] = `
<Button
OAuthSignIn={[Function]}
/>
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React, { Component } from 'react';
import withOAuth, { OAuthButton } from '../../../src/Auth/Provider/withOAuth';
import { SignInButton, Button } from '../../../src/AmplifyUI';
import { Auth } from 'aws-amplify';

describe('withOAuth test', () => {
describe('render test', () => {
test('render correctly', () => {
const MockComp = class extends Component {
render() {
return <div />;
}
}

const Comp = withOAuth(MockComp);
const wrapper = shallow(<Comp/>);
expect(wrapper).toMatchSnapshot();
});

test('render correctly with button', () => {
const wrapper = shallow(<OAuthButton/>);
expect(wrapper).toMatchSnapshot();
});
});

describe('signIn test', () => {
test('happy case with connected response', () => {
const MockComp = class extends Component {
render() {
return <div />;
}
}

const spyon = jest.spyOn(Auth, 'configure').mockImplementation(() => {
return {
oauth: {
domain: 'domain',
redirectSignIn: 'redirectUriSignIn',
redirectSignOut: 'redirectUriSignOut',
responseType: 'responseType'
},
userPoolWebClientId: 'userPoolWebClientId'
}
})
const Comp = withOAuth(MockComp);
const wrapper = shallow(<Comp/>);
const comp = wrapper.instance();

comp.signIn();

spyon.mockClear();
});
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ConfirmSignIn normal case render correctly with Props confirmSignIn 1`] = `
exports[`ConfirmSignIn render test hidden if hide include confirmSignIn 1`] = `""`;

exports[`ConfirmSignIn render test render correctly with Props confirmSignIn 1`] = `
<FormSection
theme={
Object {
Expand Down Expand Up @@ -1706,18 +1708,18 @@ exports[`ConfirmSignIn normal case render correctly with Props confirmSignIn 1`]
</FormSection>
`;

exports[`ConfirmSignIn null case with other authState render corrently 1`] = `""`;
exports[`ConfirmSignIn render test render corrently with other authstate 1`] = `""`;

exports[`ConfirmSignIn null case with other authState render corrently 2`] = `""`;
exports[`ConfirmSignIn render test render corrently with other authstate 2`] = `""`;

exports[`ConfirmSignIn null case with other authState render corrently 3`] = `""`;
exports[`ConfirmSignIn render test render corrently with other authstate 3`] = `""`;

exports[`ConfirmSignIn null case with other authState render corrently 4`] = `""`;
exports[`ConfirmSignIn render test render corrently with other authstate 4`] = `""`;

exports[`ConfirmSignIn null case with other authState render corrently 5`] = `""`;
exports[`ConfirmSignIn render test render corrently with other authstate 5`] = `""`;

exports[`ConfirmSignIn null case with other authState render corrently 6`] = `""`;
exports[`ConfirmSignIn render test render corrently with other authstate 6`] = `""`;

exports[`ConfirmSignIn null case with other authState render corrently 7`] = `""`;
exports[`ConfirmSignIn render test render corrently with other authstate 7`] = `""`;

exports[`ConfirmSignIn null case with other authState render corrently 8`] = `""`;
exports[`ConfirmSignIn render test render corrently with other authstate 8`] = `""`;
Loading

0 comments on commit 8eeef2f

Please sign in to comment.