diff --git a/samples/react-native/auth/authenticator/.gitignore b/samples/react-native/auth/authenticator/.gitignore new file mode 100644 index 0000000..d14d9bf --- /dev/null +++ b/samples/react-native/auth/authenticator/.gitignore @@ -0,0 +1,29 @@ +node_modules/**/* +.expo/* +npm-debug.* +*.jks +*.p8 +*.p12 +*.key +*.mobileprovision +*.orig.* +web-build/ + +# macOS +.DS_Store + +#amplify +amplify/\#current-cloud-backend +amplify/.config/local-* +amplify/mock-data +amplify/backend/amplify-meta.json +amplify/backend/awscloudformation +build/ +dist/ +node_modules/ +aws-exports.js +awsconfiguration.json +amplifyconfiguration.json +amplify-build-config.json +amplify-gradle-config.json +amplifytools.xcconfig diff --git a/samples/react-native/auth/authenticator/App.js b/samples/react-native/auth/authenticator/App.js new file mode 100644 index 0000000..5f170d5 --- /dev/null +++ b/samples/react-native/auth/authenticator/App.js @@ -0,0 +1,44 @@ +import { StatusBar } from "expo-status-bar"; +import React from "react"; +import { StyleSheet, Text, View, Image } from "react-native"; +import { Amplify } from "aws-amplify"; +import config from "./aws-exports"; +import { withAuthenticator } from "aws-amplify-react-native"; +import logo from "./logo.png"; + +Amplify.configure({ + ...config, + Analytics: { + disabled: true, + }, +}); + +function App() { + return ( + + + Hello World! + + + ); +} + +export default withAuthenticator(App, { + // Render a sign out button once logged in + includeGreetings: true, +}); + +const styles = StyleSheet.create({ + container: { + backgroundColor: "#fff", + alignItems: "center", + justifyContent: "center", + }, + tinyLogo: { + width: 200, + height: 200, + }, + text: { + fontSize: 50, + }, +}); diff --git a/samples/react-native/auth/authenticator/README.md b/samples/react-native/auth/authenticator/README.md new file mode 100644 index 0000000..98cf145 --- /dev/null +++ b/samples/react-native/auth/authenticator/README.md @@ -0,0 +1,72 @@ +# Authentication using Amplify withAuthenticator(HoC) Component + +## What Are We Building? + +In this example, we will use the [withAuthenticator UI Component](https://docs.amplify.aws/ui/auth/authenticator/q/framework/react-native) to allow users to signup and signin. + +We have bootstrapped this application using [Expo](https://github.com/expo/expo) and will focus on the logic that adds authentication. + +### web +screenshot + +### iOS +screenshot + +## Let's Get Started! + +## Setup + +1. Login or [Create](https://portal.aws.amazon.com/billing/signup?type=enterprise#/start) an AWS Account. +2. In a terminal, clone this repo. +3. Install the [Amplify CLI](https://github.com/aws-amplify/amplify-cli) `npm install -g @aws-amplify/cli`. Ensure you are in the directory you just cloned. +4. If it’s your first time using the CLI, you will need to configure it by running `amplify configure`. Follow the instructions to create an IAM profile locally. +5. Now let’s initialize an Amplify project in this directory. `amplify init` + +``` +$ amplify init +? Enter a name for the environment dev +? Choose your default editor: (pick an editor) +? Do you want to use an AWS profile? Y (this should be the profile you created in step #4) +``` + +6. Lastly, the CLI will provision our backend resources using the config files in the `amplify` directory. To provision these resources in the cloud, run `amplify push`. Confirm you want to use the Auth category. + +## Running the App + +1. `npm install` +2. `npm run start` +3. The app should run on http://localhost:19006/. Try creating an account and signing in. + +## A Closer Look + +Now that you've built the app, let's take a look under the hood and explore how it works. + +### Frontend + +Let's dive into the frontend components. In this app, the important logic is in `App.js`. + +The `withAuthenticator` component renders your App component after a successful user signed in, and it prevents non-sign-in users to interact with your app. In this case, we need to display a sign-out button to trigger the related process. + +To display a sign-out button or customize other, set includeGreetings = true in the parameter object. It displays a greetings section on top of your app, and a sign-out button is displayed in the authenticated state. + +``` +export default withAuthenticator(App, { + // Render a sign out button once logged in + includeGreetings: true, +}); +``` + +This works out of the box and displays a standard SignIn/SignOut interface for users. + +`Note`: The withAuthenticator HOC wraps an Authenticator component. Using [Authenticator](https://docs.amplify.aws/ui/auth/authenticator/q/framework/react-native#using-the-authenticator-component) directly gives you more customization options for your UI. + +### Backend + +The Amplify CLI is responsible for building the necessary modules in the backend to support user authentication, which in this case lives in the AWS cloud. To be more specific, we will use Amazon Cognito, so there is no need to build your own backend. + +The config files in the `amplify` folder contain these instructions. If you start from scratch, run `amplify add auth` to build these files. Then run, `amplify push` to deploy them on AWS. + +## Next Steps + +- Read our [Authentication documentation](https://docs.amplify.aws/ui/auth/authenticator/q/framework/react-native) to further customize your app. There are many advanced use cases to build upon. +- Join our communities on [Twitter](https://twitter.com/awsamplify) and [Discord](https://discord.gg/amplify) diff --git a/samples/react-native/auth/authenticator/amplify/.config/project-config.json b/samples/react-native/auth/authenticator/amplify/.config/project-config.json new file mode 100644 index 0000000..17c99f5 --- /dev/null +++ b/samples/react-native/auth/authenticator/amplify/.config/project-config.json @@ -0,0 +1,17 @@ +{ + "projectName": "rnamplify", + "version": "3.0", + "frontend": "javascript", + "javascript": { + "framework": "react-native", + "config": { + "SourceDir": "./", + "DistributionDir": "./", + "BuildCommand": "npm run-script build", + "StartCommand": "npm run-script start" + } + }, + "providers": [ + "awscloudformation" + ] +} \ No newline at end of file diff --git a/samples/react-native/auth/authenticator/amplify/backend/auth/rnamplify17e39fbe/parameters.json b/samples/react-native/auth/authenticator/amplify/backend/auth/rnamplify17e39fbe/parameters.json new file mode 100644 index 0000000..712654e --- /dev/null +++ b/samples/react-native/auth/authenticator/amplify/backend/auth/rnamplify17e39fbe/parameters.json @@ -0,0 +1,52 @@ +{ + "identityPoolName": "rnamplify17e39fbe_identitypool_17e39fbe", + "allowUnauthenticatedIdentities": false, + "resourceNameTruncated": "rnampl17e39fbe", + "userPoolName": "rnamplify17e39fbe_userpool_17e39fbe", + "autoVerifiedAttributes": [ + "email" + ], + "mfaConfiguration": "OFF", + "mfaTypes": [ + "SMS Text Message" + ], + "smsAuthenticationMessage": "Your authentication code is {####}", + "smsVerificationMessage": "Your verification code is {####}", + "emailVerificationSubject": "Your verification code", + "emailVerificationMessage": "Your verification code is {####}", + "defaultPasswordPolicy": false, + "passwordPolicyMinLength": 8, + "passwordPolicyCharacters": [], + "requiredAttributes": [ + "email" + ], + "userpoolClientGenerateSecret": true, + "userpoolClientRefreshTokenValidity": 30, + "userpoolClientWriteAttributes": [ + "email" + ], + "userpoolClientReadAttributes": [ + "email" + ], + "userpoolClientLambdaRole": "rnampl17e39fbe_userpoolclient_lambda_role", + "userpoolClientSetAttributes": false, + "sharedId": "17e39fbe", + "resourceName": "rnamplify17e39fbe", + "authSelections": "identityPoolAndUserPool", + "authRoleArn": { + "Fn::GetAtt": [ + "AuthRole", + "Arn" + ] + }, + "unauthRoleArn": { + "Fn::GetAtt": [ + "UnauthRole", + "Arn" + ] + }, + "useDefault": "default", + "userPoolGroupList": [], + "serviceName": "Cognito", + "dependsOn": [] +} \ No newline at end of file diff --git a/samples/react-native/auth/authenticator/amplify/backend/auth/rnamplify17e39fbe/rnamplify17e39fbe-cloudformation-template.yml b/samples/react-native/auth/authenticator/amplify/backend/auth/rnamplify17e39fbe/rnamplify17e39fbe-cloudformation-template.yml new file mode 100644 index 0000000..ce5956e --- /dev/null +++ b/samples/react-native/auth/authenticator/amplify/backend/auth/rnamplify17e39fbe/rnamplify17e39fbe-cloudformation-template.yml @@ -0,0 +1,370 @@ +AWSTemplateFormatVersion: 2010-09-09 + +Parameters: + env: + Type: String + authRoleArn: + Type: String + unauthRoleArn: + Type: String + + + + + identityPoolName: + Type: String + + allowUnauthenticatedIdentities: + Type: String + + resourceNameTruncated: + Type: String + + userPoolName: + Type: String + + autoVerifiedAttributes: + Type: CommaDelimitedList + + mfaConfiguration: + Type: String + + mfaTypes: + Type: CommaDelimitedList + + smsAuthenticationMessage: + Type: String + + smsVerificationMessage: + Type: String + + emailVerificationSubject: + Type: String + + emailVerificationMessage: + Type: String + + defaultPasswordPolicy: + Type: String + + passwordPolicyMinLength: + Type: Number + + passwordPolicyCharacters: + Type: CommaDelimitedList + + requiredAttributes: + Type: CommaDelimitedList + + userpoolClientGenerateSecret: + Type: String + + userpoolClientRefreshTokenValidity: + Type: Number + + userpoolClientWriteAttributes: + Type: CommaDelimitedList + + userpoolClientReadAttributes: + Type: CommaDelimitedList + + userpoolClientLambdaRole: + Type: String + + userpoolClientSetAttributes: + Type: String + + sharedId: + Type: String + + resourceName: + Type: String + + authSelections: + Type: String + + useDefault: + Type: String + + userPoolGroupList: + Type: CommaDelimitedList + + serviceName: + Type: String + + dependsOn: + Type: CommaDelimitedList + +Conditions: + ShouldNotCreateEnvResources: !Equals [ !Ref env, NONE ] + +Resources: + + + # BEGIN SNS ROLE RESOURCE + SNSRole: + # Created to allow the UserPool SMS Config to publish via the Simple Notification Service during MFA Process + Type: AWS::IAM::Role + Properties: + RoleName: !If [ShouldNotCreateEnvResources, 'rnampl17e39fbe_sns-role', !Join ['',[ 'sns', '17e39fbe', !Select [3, !Split ['-', !Ref 'AWS::StackName']], '-', !Ref env]]] + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Sid: "" + Effect: "Allow" + Principal: + Service: "cognito-idp.amazonaws.com" + Action: + - "sts:AssumeRole" + Condition: + StringEquals: + sts:ExternalId: rnampl17e39fbe_role_external_id + Policies: + - + PolicyName: rnampl17e39fbe-sns-policy + PolicyDocument: + Version: "2012-10-17" + Statement: + - + Effect: "Allow" + Action: + - "sns:Publish" + Resource: "*" + # BEGIN USER POOL RESOURCES + UserPool: + # Created upon user selection + # Depends on SNS Role for Arn if MFA is enabled + Type: AWS::Cognito::UserPool + UpdateReplacePolicy: Retain + Properties: + UserPoolName: !If [ShouldNotCreateEnvResources, !Ref userPoolName, !Join ['',[!Ref userPoolName, '-', !Ref env]]] + + Schema: + + - + Name: email + Required: true + Mutable: true + + + + + AutoVerifiedAttributes: !Ref autoVerifiedAttributes + + + EmailVerificationMessage: !Ref emailVerificationMessage + EmailVerificationSubject: !Ref emailVerificationSubject + + Policies: + PasswordPolicy: + MinimumLength: !Ref passwordPolicyMinLength + RequireLowercase: false + RequireNumbers: false + RequireSymbols: false + RequireUppercase: false + + MfaConfiguration: !Ref mfaConfiguration + SmsVerificationMessage: !Ref smsVerificationMessage + SmsConfiguration: + SnsCallerArn: !GetAtt SNSRole.Arn + ExternalId: rnampl17e39fbe_role_external_id + + + UserPoolClientWeb: + # Created provide application access to user pool + # Depends on UserPool for ID reference + Type: "AWS::Cognito::UserPoolClient" + Properties: + ClientName: rnampl17e39fbe_app_clientWeb + + RefreshTokenValidity: !Ref userpoolClientRefreshTokenValidity + UserPoolId: !Ref UserPool + DependsOn: UserPool + UserPoolClient: + # Created provide application access to user pool + # Depends on UserPool for ID reference + Type: "AWS::Cognito::UserPoolClient" + Properties: + ClientName: rnampl17e39fbe_app_client + + GenerateSecret: !Ref userpoolClientGenerateSecret + RefreshTokenValidity: !Ref userpoolClientRefreshTokenValidity + UserPoolId: !Ref UserPool + DependsOn: UserPool + # BEGIN USER POOL LAMBDA RESOURCES + UserPoolClientRole: + # Created to execute Lambda which gets userpool app client config values + Type: 'AWS::IAM::Role' + Properties: + RoleName: !If [ShouldNotCreateEnvResources, !Ref userpoolClientLambdaRole, !Join ['',['upClientLambdaRole', '17e39fbe', !Select [3, !Split ['-', !Ref 'AWS::StackName']], '-', !Ref env]]] + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - 'sts:AssumeRole' + DependsOn: UserPoolClient + UserPoolClientLambda: + # Lambda which gets userpool app client config values + # Depends on UserPool for id + # Depends on UserPoolClientRole for role ARN + Type: 'AWS::Lambda::Function' + Properties: + Code: + ZipFile: !Join + - |+ + - - 'const response = require(''cfn-response'');' + - 'const aws = require(''aws-sdk'');' + - 'const identity = new aws.CognitoIdentityServiceProvider();' + - 'exports.handler = (event, context, callback) => {' + - ' if (event.RequestType == ''Delete'') { ' + - ' response.send(event, context, response.SUCCESS, {})' + - ' }' + - ' if (event.RequestType == ''Update'' || event.RequestType == ''Create'') {' + - ' const params = {' + - ' ClientId: event.ResourceProperties.clientId,' + - ' UserPoolId: event.ResourceProperties.userpoolId' + - ' };' + - ' identity.describeUserPoolClient(params).promise()' + - ' .then((res) => {' + - ' response.send(event, context, response.SUCCESS, {''appSecret'': res.UserPoolClient.ClientSecret});' + - ' })' + - ' .catch((err) => {' + - ' response.send(event, context, response.FAILED, {err});' + - ' });' + - ' }' + - '};' + Handler: index.handler + Runtime: nodejs10.x + Timeout: '300' + Role: !GetAtt + - UserPoolClientRole + - Arn + DependsOn: UserPoolClientRole + UserPoolClientLambdaPolicy: + # Sets userpool policy for the role that executes the Userpool Client Lambda + # Depends on UserPool for Arn + # Marked as depending on UserPoolClientRole for easier to understand CFN sequencing + Type: 'AWS::IAM::Policy' + Properties: + PolicyName: rnampl17e39fbe_userpoolclient_lambda_iam_policy + Roles: + - !Ref UserPoolClientRole + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - 'cognito-idp:DescribeUserPoolClient' + Resource: !GetAtt UserPool.Arn + DependsOn: UserPoolClientLambda + UserPoolClientLogPolicy: + # Sets log policy for the role that executes the Userpool Client Lambda + # Depends on UserPool for Arn + # Marked as depending on UserPoolClientLambdaPolicy for easier to understand CFN sequencing + Type: 'AWS::IAM::Policy' + Properties: + PolicyName: rnampl17e39fbe_userpoolclient_lambda_log_policy + Roles: + - !Ref UserPoolClientRole + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - 'logs:CreateLogGroup' + - 'logs:CreateLogStream' + - 'logs:PutLogEvents' + Resource: !Sub + - arn:aws:logs:${region}:${account}:log-group:/aws/lambda/${lambda}:log-stream:* + - { region: !Ref "AWS::Region", account: !Ref "AWS::AccountId", lambda: !Ref UserPoolClientLambda} + DependsOn: UserPoolClientLambdaPolicy + UserPoolClientInputs: + # Values passed to Userpool client Lambda + # Depends on UserPool for Id + # Depends on UserPoolClient for Id + # Marked as depending on UserPoolClientLambdaPolicy for easier to understand CFN sequencing + Type: 'Custom::LambdaCallout' + Properties: + ServiceToken: !GetAtt UserPoolClientLambda.Arn + clientId: !Ref UserPoolClient + userpoolId: !Ref UserPool + DependsOn: UserPoolClientLogPolicy + + + + + + + + # BEGIN IDENTITY POOL RESOURCES + + + IdentityPool: + # Always created + Type: AWS::Cognito::IdentityPool + Properties: + IdentityPoolName: !If [ShouldNotCreateEnvResources, 'rnamplify17e39fbe_identitypool_17e39fbe', !Join ['',['rnamplify17e39fbe_identitypool_17e39fbe', '__', !Ref env]]] + + CognitoIdentityProviders: + - ClientId: !Ref UserPoolClient + ProviderName: !Sub + - cognito-idp.${region}.amazonaws.com/${client} + - { region: !Ref "AWS::Region", client: !Ref UserPool} + - ClientId: !Ref UserPoolClientWeb + ProviderName: !Sub + - cognito-idp.${region}.amazonaws.com/${client} + - { region: !Ref "AWS::Region", client: !Ref UserPool} + + AllowUnauthenticatedIdentities: !Ref allowUnauthenticatedIdentities + + + DependsOn: UserPoolClientInputs + + + IdentityPoolRoleMap: + # Created to map Auth and Unauth roles to the identity pool + # Depends on Identity Pool for ID ref + Type: AWS::Cognito::IdentityPoolRoleAttachment + Properties: + IdentityPoolId: !Ref IdentityPool + Roles: + unauthenticated: !Ref unauthRoleArn + authenticated: !Ref authRoleArn + DependsOn: IdentityPool + + +Outputs : + + IdentityPoolId: + Value: !Ref 'IdentityPool' + Description: Id for the identity pool + IdentityPoolName: + Value: !GetAtt IdentityPool.Name + + + + + UserPoolId: + Value: !Ref 'UserPool' + Description: Id for the user pool + UserPoolName: + Value: !Ref userPoolName + AppClientIDWeb: + Value: !Ref 'UserPoolClientWeb' + Description: The user pool app client id for web + AppClientID: + Value: !Ref 'UserPoolClient' + Description: The user pool app client id + AppClientSecret: + Value: !GetAtt UserPoolClientInputs.appSecret + + + + + + + diff --git a/samples/react-native/auth/authenticator/amplify/backend/backend-config.json b/samples/react-native/auth/authenticator/amplify/backend/backend-config.json new file mode 100644 index 0000000..9a682cb --- /dev/null +++ b/samples/react-native/auth/authenticator/amplify/backend/backend-config.json @@ -0,0 +1,10 @@ +{ + "auth": { + "rnamplify17e39fbe": { + "service": "Cognito", + "providerPlugin": "awscloudformation", + "dependsOn": [], + "customAuth": false + } + } +} \ No newline at end of file diff --git a/samples/react-native/auth/authenticator/amplify/backend/tags.json b/samples/react-native/auth/authenticator/amplify/backend/tags.json new file mode 100644 index 0000000..b9321d7 --- /dev/null +++ b/samples/react-native/auth/authenticator/amplify/backend/tags.json @@ -0,0 +1,10 @@ +[ + { + "Key": "user:Stack", + "Value": "{project-env}" + }, + { + "Key": "user:Application", + "Value": "{project-name}" + } +] \ No newline at end of file diff --git a/samples/react-native/auth/authenticator/amplify/cli.json b/samples/react-native/auth/authenticator/amplify/cli.json new file mode 100644 index 0000000..0b37b4e --- /dev/null +++ b/samples/react-native/auth/authenticator/amplify/cli.json @@ -0,0 +1,7 @@ +{ + "features": { + "graphqltransformer": { + "addmissingownerfields": true + } + } +} \ No newline at end of file diff --git a/samples/react-native/auth/authenticator/app.json b/samples/react-native/auth/authenticator/app.json new file mode 100644 index 0000000..ddca2d3 --- /dev/null +++ b/samples/react-native/auth/authenticator/app.json @@ -0,0 +1,32 @@ +{ + "expo": { + "name": "authenticator", + "slug": "authenticator", + "version": "1.0.0", + "orientation": "portrait", + "icon": "./assets/icon.png", + "splash": { + "image": "./assets/splash.png", + "resizeMode": "contain", + "backgroundColor": "#ffffff" + }, + "updates": { + "fallbackToCacheTimeout": 0 + }, + "assetBundlePatterns": [ + "**/*" + ], + "ios": { + "supportsTablet": true + }, + "android": { + "adaptiveIcon": { + "foregroundImage": "./assets/adaptive-icon.png", + "backgroundColor": "#FFFFFF" + } + }, + "web": { + "favicon": "./assets/favicon.png" + } + } +} diff --git a/samples/react-native/auth/authenticator/assets/adaptive-icon.png b/samples/react-native/auth/authenticator/assets/adaptive-icon.png new file mode 100644 index 0000000..03d6f6b Binary files /dev/null and b/samples/react-native/auth/authenticator/assets/adaptive-icon.png differ diff --git a/samples/react-native/auth/authenticator/assets/favicon.png b/samples/react-native/auth/authenticator/assets/favicon.png new file mode 100644 index 0000000..e75f697 Binary files /dev/null and b/samples/react-native/auth/authenticator/assets/favicon.png differ diff --git a/samples/react-native/auth/authenticator/assets/icon.png b/samples/react-native/auth/authenticator/assets/icon.png new file mode 100644 index 0000000..a0b1526 Binary files /dev/null and b/samples/react-native/auth/authenticator/assets/icon.png differ diff --git a/samples/react-native/auth/authenticator/assets/splash.png b/samples/react-native/auth/authenticator/assets/splash.png new file mode 100644 index 0000000..6f47774 Binary files /dev/null and b/samples/react-native/auth/authenticator/assets/splash.png differ diff --git a/samples/react-native/auth/authenticator/babel.config.js b/samples/react-native/auth/authenticator/babel.config.js new file mode 100644 index 0000000..2900afe --- /dev/null +++ b/samples/react-native/auth/authenticator/babel.config.js @@ -0,0 +1,6 @@ +module.exports = function(api) { + api.cache(true); + return { + presets: ['babel-preset-expo'], + }; +}; diff --git a/samples/react-native/auth/authenticator/logo.png b/samples/react-native/auth/authenticator/logo.png new file mode 100644 index 0000000..e20fb7b Binary files /dev/null and b/samples/react-native/auth/authenticator/logo.png differ diff --git a/samples/react-native/auth/authenticator/logo.svg b/samples/react-native/auth/authenticator/logo.svg new file mode 100644 index 0000000..6b60c10 --- /dev/null +++ b/samples/react-native/auth/authenticator/logo.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/samples/react-native/auth/authenticator/package.json b/samples/react-native/auth/authenticator/package.json new file mode 100644 index 0000000..06a760f --- /dev/null +++ b/samples/react-native/auth/authenticator/package.json @@ -0,0 +1,26 @@ +{ + "main": "node_modules/expo/AppEntry.js", + "scripts": { + "start": "expo start", + "android": "expo start --android", + "ios": "expo start --ios", + "web": "expo start --web", + "eject": "expo eject" + }, + "dependencies": { + "@aws-amplify/ui-components": "^0.9.2", + "@react-native-community/netinfo": "^5.9.7", + "aws-amplify": "^3.3.7", + "aws-amplify-react-native": "^4.2.8", + "expo": "~39.0.2", + "expo-status-bar": "~1.0.2", + "react": "16.13.1", + "react-dom": "16.13.1", + "react-native": "https://github.com/expo/react-native/archive/sdk-39.0.4.tar.gz", + "react-native-web": "~0.13.12" + }, + "devDependencies": { + "@babel/core": "~7.9.0" + }, + "private": true +}