├── .gitignore ├── .vscode └── settings.json ├── README.md ├── amplify ├── .config │ └── project-config.json ├── README.md ├── backend │ ├── auth │ │ └── dlwamplifyauthdemo4f490bc5 │ │ │ ├── dlwamplifyauthdemo4f490bc5-cloudformation-template.yml │ │ │ └── parameters.json │ ├── backend-config.json │ └── tags.json ├── cli.json └── team-provider-info.json ├── package.json ├── postcss.config.js ├── public ├── favicon.ico └── vercel.svg ├── src ├── components │ ├── confirm.js │ ├── register.js │ ├── sign-in.js │ └── sign-out.js └── pages │ ├── _app.js │ ├── api │ └── hello.js │ ├── client-protected.js │ ├── index.js │ └── server-protected.js ├── tailwind.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | #amplify 37 | amplify/\#current-cloud-backend 38 | amplify/.config/local-* 39 | amplify/logs 40 | amplify/mock-data 41 | amplify/backend/amplify-meta.json 42 | amplify/backend/awscloudformation 43 | amplify/backend/.temp 44 | build/ 45 | dist/ 46 | node_modules/ 47 | aws-exports.js 48 | awsconfiguration.json 49 | amplifyconfiguration.json 50 | amplify-build-config.json 51 | amplify-gradle-config.json 52 | amplifytools.xcconfig 53 | .secret-* -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "amplify/.config": true, 4 | "amplify/**/*-parameters.json": true, 5 | "amplify/**/amplify.state": true, 6 | "amplify/**/transform.conf.json": true, 7 | "amplify/#current-cloud-backend": true, 8 | "amplify/backend/amplify-meta.json": true, 9 | "amplify/backend/awscloudformation": true 10 | } 11 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a companion repo for the blog https://darrenwhite.dev/blog/custom-ui-amplify-nextjs where I create a custom authentication UI with the AWS amplify auth class. 2 | 3 | Built with 4 | - [Next.js](https://nextjs.org/) 5 | - [React Hook Form](https://react-hook-form.com/) 6 | - [AWS Amplify](https://aws.amazon.com/amplify/) 7 | - [Tailwind CSS](https://tailwindcss.com/) 8 | 9 | 10 | ## Getting Started 11 | 12 | First, run the development server: 13 | 14 | ```bash 15 | npm run dev 16 | # or 17 | yarn dev 18 | ``` 19 | 20 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. -------------------------------------------------------------------------------- /amplify/.config/project-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "dlwAmplifyAuthDemo", 3 | "version": "3.0", 4 | "frontend": "javascript", 5 | "javascript": { 6 | "framework": "react", 7 | "config": { 8 | "SourceDir": "src", 9 | "DistributionDir": "build", 10 | "BuildCommand": "npm run-script build", 11 | "StartCommand": "npm run-script start" 12 | } 13 | }, 14 | "providers": [ 15 | "awscloudformation" 16 | ] 17 | } -------------------------------------------------------------------------------- /amplify/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Amplify CLI 2 | This directory was generated by [Amplify CLI](https://docs.amplify.aws/cli). 3 | 4 | Helpful resources: 5 | - Amplify documentation: https://docs.amplify.aws 6 | - Amplify CLI documentation: https://docs.amplify.aws/cli 7 | - More details on this folder & generated files: https://docs.amplify.aws/cli/reference/files 8 | - Join Amplify's community: https://amplify.aws/community/ 9 | -------------------------------------------------------------------------------- /amplify/backend/auth/dlwamplifyauthdemo4f490bc5/dlwamplifyauthdemo4f490bc5-cloudformation-template.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | 3 | Parameters: 4 | env: 5 | Type: String 6 | authRoleArn: 7 | Type: String 8 | unauthRoleArn: 9 | Type: String 10 | 11 | 12 | 13 | 14 | identityPoolName: 15 | Type: String 16 | 17 | 18 | 19 | allowUnauthenticatedIdentities: 20 | Type: String 21 | 22 | resourceNameTruncated: 23 | Type: String 24 | 25 | 26 | userPoolName: 27 | Type: String 28 | 29 | 30 | 31 | autoVerifiedAttributes: 32 | Type: CommaDelimitedList 33 | 34 | mfaConfiguration: 35 | Type: String 36 | 37 | 38 | 39 | mfaTypes: 40 | Type: CommaDelimitedList 41 | 42 | smsAuthenticationMessage: 43 | Type: String 44 | 45 | 46 | smsVerificationMessage: 47 | Type: String 48 | 49 | 50 | emailVerificationSubject: 51 | Type: String 52 | 53 | 54 | emailVerificationMessage: 55 | Type: String 56 | 57 | 58 | 59 | defaultPasswordPolicy: 60 | Type: String 61 | 62 | 63 | passwordPolicyMinLength: 64 | Type: Number 65 | 66 | 67 | passwordPolicyCharacters: 68 | Type: CommaDelimitedList 69 | 70 | 71 | requiredAttributes: 72 | Type: CommaDelimitedList 73 | 74 | 75 | userpoolClientGenerateSecret: 76 | Type: String 77 | 78 | 79 | userpoolClientRefreshTokenValidity: 80 | Type: Number 81 | 82 | 83 | userpoolClientWriteAttributes: 84 | Type: CommaDelimitedList 85 | 86 | 87 | userpoolClientReadAttributes: 88 | Type: CommaDelimitedList 89 | 90 | userpoolClientLambdaRole: 91 | Type: String 92 | 93 | 94 | 95 | userpoolClientSetAttributes: 96 | Type: String 97 | 98 | sharedId: 99 | Type: String 100 | 101 | 102 | resourceName: 103 | Type: String 104 | 105 | 106 | authSelections: 107 | Type: String 108 | 109 | 110 | 111 | 112 | useDefault: 113 | Type: String 114 | 115 | 116 | 117 | userPoolGroupList: 118 | Type: CommaDelimitedList 119 | 120 | 121 | serviceName: 122 | Type: String 123 | 124 | 125 | 126 | usernameCaseSensitive: 127 | Type: String 128 | 129 | 130 | dependsOn: 131 | Type: CommaDelimitedList 132 | 133 | Conditions: 134 | ShouldNotCreateEnvResources: !Equals [ !Ref env, NONE ] 135 | 136 | ShouldOutputAppClientSecrets: !Equals [!Ref userpoolClientGenerateSecret, true ] 137 | 138 | 139 | Resources: 140 | 141 | 142 | # BEGIN SNS ROLE RESOURCE 143 | SNSRole: 144 | # Created to allow the UserPool SMS Config to publish via the Simple Notification Service during MFA Process 145 | Type: AWS::IAM::Role 146 | Properties: 147 | RoleName: !If [ShouldNotCreateEnvResources, 'dlwamp4f490bc5_sns-role', !Join ['',[ 'sns', '4f490bc5', !Select [3, !Split ['-', !Ref 'AWS::StackName']], '-', !Ref env]]] 148 | AssumeRolePolicyDocument: 149 | Version: "2012-10-17" 150 | Statement: 151 | - Sid: "" 152 | Effect: "Allow" 153 | Principal: 154 | Service: "cognito-idp.amazonaws.com" 155 | Action: 156 | - "sts:AssumeRole" 157 | Condition: 158 | StringEquals: 159 | sts:ExternalId: dlwamp4f490bc5_role_external_id 160 | Policies: 161 | - 162 | PolicyName: dlwamp4f490bc5-sns-policy 163 | PolicyDocument: 164 | Version: "2012-10-17" 165 | Statement: 166 | - 167 | Effect: "Allow" 168 | Action: 169 | - "sns:Publish" 170 | Resource: "*" 171 | # BEGIN USER POOL RESOURCES 172 | UserPool: 173 | # Created upon user selection 174 | # Depends on SNS Role for Arn if MFA is enabled 175 | Type: AWS::Cognito::UserPool 176 | UpdateReplacePolicy: Retain 177 | Properties: 178 | UserPoolName: !If [ShouldNotCreateEnvResources, !Ref userPoolName, !Join ['',[!Ref userPoolName, '-', !Ref env]]] 179 | 180 | 181 | UsernameConfiguration: 182 | CaseSensitive: false 183 | 184 | Schema: 185 | 186 | - 187 | Name: email 188 | Required: true 189 | Mutable: true 190 | 191 | 192 | 193 | 194 | AutoVerifiedAttributes: !Ref autoVerifiedAttributes 195 | 196 | 197 | EmailVerificationMessage: !Ref emailVerificationMessage 198 | EmailVerificationSubject: !Ref emailVerificationSubject 199 | 200 | Policies: 201 | PasswordPolicy: 202 | MinimumLength: !Ref passwordPolicyMinLength 203 | RequireLowercase: false 204 | RequireNumbers: false 205 | RequireSymbols: false 206 | RequireUppercase: false 207 | 208 | MfaConfiguration: !Ref mfaConfiguration 209 | SmsVerificationMessage: !Ref smsVerificationMessage 210 | SmsConfiguration: 211 | SnsCallerArn: !GetAtt SNSRole.Arn 212 | ExternalId: dlwamp4f490bc5_role_external_id 213 | 214 | 215 | UserPoolClientWeb: 216 | # Created provide application access to user pool 217 | # Depends on UserPool for ID reference 218 | Type: "AWS::Cognito::UserPoolClient" 219 | Properties: 220 | ClientName: dlwamp4f490bc5_app_clientWeb 221 | 222 | RefreshTokenValidity: !Ref userpoolClientRefreshTokenValidity 223 | UserPoolId: !Ref UserPool 224 | DependsOn: UserPool 225 | UserPoolClient: 226 | # Created provide application access to user pool 227 | # Depends on UserPool for ID reference 228 | Type: "AWS::Cognito::UserPoolClient" 229 | Properties: 230 | ClientName: dlwamp4f490bc5_app_client 231 | 232 | GenerateSecret: !Ref userpoolClientGenerateSecret 233 | RefreshTokenValidity: !Ref userpoolClientRefreshTokenValidity 234 | UserPoolId: !Ref UserPool 235 | DependsOn: UserPool 236 | # BEGIN USER POOL LAMBDA RESOURCES 237 | UserPoolClientRole: 238 | # Created to execute Lambda which gets userpool app client config values 239 | Type: 'AWS::IAM::Role' 240 | Properties: 241 | RoleName: !If [ShouldNotCreateEnvResources, !Ref userpoolClientLambdaRole, !Join ['',['upClientLambdaRole', '4f490bc5', !Select [3, !Split ['-', !Ref 'AWS::StackName']], '-', !Ref env]]] 242 | AssumeRolePolicyDocument: 243 | Version: '2012-10-17' 244 | Statement: 245 | - Effect: Allow 246 | Principal: 247 | Service: 248 | - lambda.amazonaws.com 249 | Action: 250 | - 'sts:AssumeRole' 251 | DependsOn: UserPoolClient 252 | UserPoolClientLambda: 253 | # Lambda which gets userpool app client config values 254 | # Depends on UserPool for id 255 | # Depends on UserPoolClientRole for role ARN 256 | Type: 'AWS::Lambda::Function' 257 | Properties: 258 | Code: 259 | ZipFile: !Join 260 | - |+ 261 | - - 'const response = require(''cfn-response'');' 262 | - 'const aws = require(''aws-sdk'');' 263 | - 'const identity = new aws.CognitoIdentityServiceProvider();' 264 | - 'exports.handler = (event, context, callback) => {' 265 | - ' if (event.RequestType == ''Delete'') { ' 266 | - ' response.send(event, context, response.SUCCESS, {})' 267 | - ' }' 268 | - ' if (event.RequestType == ''Update'' || event.RequestType == ''Create'') {' 269 | - ' const params = {' 270 | - ' ClientId: event.ResourceProperties.clientId,' 271 | - ' UserPoolId: event.ResourceProperties.userpoolId' 272 | - ' };' 273 | - ' identity.describeUserPoolClient(params).promise()' 274 | - ' .then((res) => {' 275 | - ' response.send(event, context, response.SUCCESS, {''appSecret'': res.UserPoolClient.ClientSecret});' 276 | - ' })' 277 | - ' .catch((err) => {' 278 | - ' response.send(event, context, response.FAILED, {err});' 279 | - ' });' 280 | - ' }' 281 | - '};' 282 | Handler: index.handler 283 | Runtime: nodejs10.x 284 | Timeout: '300' 285 | Role: !GetAtt 286 | - UserPoolClientRole 287 | - Arn 288 | DependsOn: UserPoolClientRole 289 | UserPoolClientLambdaPolicy: 290 | # Sets userpool policy for the role that executes the Userpool Client Lambda 291 | # Depends on UserPool for Arn 292 | # Marked as depending on UserPoolClientRole for easier to understand CFN sequencing 293 | Type: 'AWS::IAM::Policy' 294 | Properties: 295 | PolicyName: dlwamp4f490bc5_userpoolclient_lambda_iam_policy 296 | Roles: 297 | - !Ref UserPoolClientRole 298 | PolicyDocument: 299 | Version: '2012-10-17' 300 | Statement: 301 | - Effect: Allow 302 | Action: 303 | - 'cognito-idp:DescribeUserPoolClient' 304 | Resource: !GetAtt UserPool.Arn 305 | DependsOn: UserPoolClientLambda 306 | UserPoolClientLogPolicy: 307 | # Sets log policy for the role that executes the Userpool Client Lambda 308 | # Depends on UserPool for Arn 309 | # Marked as depending on UserPoolClientLambdaPolicy for easier to understand CFN sequencing 310 | Type: 'AWS::IAM::Policy' 311 | Properties: 312 | PolicyName: dlwamp4f490bc5_userpoolclient_lambda_log_policy 313 | Roles: 314 | - !Ref UserPoolClientRole 315 | PolicyDocument: 316 | Version: 2012-10-17 317 | Statement: 318 | - Effect: Allow 319 | Action: 320 | - 'logs:CreateLogGroup' 321 | - 'logs:CreateLogStream' 322 | - 'logs:PutLogEvents' 323 | Resource: !Sub 324 | - arn:aws:logs:${region}:${account}:log-group:/aws/lambda/${lambda}:log-stream:* 325 | - { region: !Ref "AWS::Region", account: !Ref "AWS::AccountId", lambda: !Ref UserPoolClientLambda} 326 | DependsOn: UserPoolClientLambdaPolicy 327 | UserPoolClientInputs: 328 | # Values passed to Userpool client Lambda 329 | # Depends on UserPool for Id 330 | # Depends on UserPoolClient for Id 331 | # Marked as depending on UserPoolClientLambdaPolicy for easier to understand CFN sequencing 332 | Type: 'Custom::LambdaCallout' 333 | Properties: 334 | ServiceToken: !GetAtt UserPoolClientLambda.Arn 335 | clientId: !Ref UserPoolClient 336 | userpoolId: !Ref UserPool 337 | DependsOn: UserPoolClientLogPolicy 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | # BEGIN IDENTITY POOL RESOURCES 346 | 347 | 348 | IdentityPool: 349 | # Always created 350 | Type: AWS::Cognito::IdentityPool 351 | Properties: 352 | IdentityPoolName: !If [ShouldNotCreateEnvResources, 'dlwamplifyauthdemo4f490bc5_identitypool_4f490bc5', !Join ['',['dlwamplifyauthdemo4f490bc5_identitypool_4f490bc5', '__', !Ref env]]] 353 | 354 | CognitoIdentityProviders: 355 | - ClientId: !Ref UserPoolClient 356 | ProviderName: !Sub 357 | - cognito-idp.${region}.amazonaws.com/${client} 358 | - { region: !Ref "AWS::Region", client: !Ref UserPool} 359 | - ClientId: !Ref UserPoolClientWeb 360 | ProviderName: !Sub 361 | - cognito-idp.${region}.amazonaws.com/${client} 362 | - { region: !Ref "AWS::Region", client: !Ref UserPool} 363 | 364 | AllowUnauthenticatedIdentities: !Ref allowUnauthenticatedIdentities 365 | 366 | 367 | DependsOn: UserPoolClientInputs 368 | 369 | 370 | IdentityPoolRoleMap: 371 | # Created to map Auth and Unauth roles to the identity pool 372 | # Depends on Identity Pool for ID ref 373 | Type: AWS::Cognito::IdentityPoolRoleAttachment 374 | Properties: 375 | IdentityPoolId: !Ref IdentityPool 376 | Roles: 377 | unauthenticated: !Ref unauthRoleArn 378 | authenticated: !Ref authRoleArn 379 | DependsOn: IdentityPool 380 | 381 | 382 | Outputs : 383 | 384 | IdentityPoolId: 385 | Value: !Ref 'IdentityPool' 386 | Description: Id for the identity pool 387 | IdentityPoolName: 388 | Value: !GetAtt IdentityPool.Name 389 | 390 | 391 | 392 | 393 | UserPoolId: 394 | Value: !Ref 'UserPool' 395 | Description: Id for the user pool 396 | UserPoolName: 397 | Value: !Ref userPoolName 398 | AppClientIDWeb: 399 | Value: !Ref 'UserPoolClientWeb' 400 | Description: The user pool app client id for web 401 | AppClientID: 402 | Value: !Ref 'UserPoolClient' 403 | Description: The user pool app client id 404 | AppClientSecret: 405 | Value: !GetAtt UserPoolClientInputs.appSecret 406 | Condition: ShouldOutputAppClientSecrets 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | -------------------------------------------------------------------------------- /amplify/backend/auth/dlwamplifyauthdemo4f490bc5/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "identityPoolName": "dlwamplifyauthdemo4f490bc5_identitypool_4f490bc5", 3 | "allowUnauthenticatedIdentities": false, 4 | "resourceNameTruncated": "dlwamp4f490bc5", 5 | "userPoolName": "dlwamplifyauthdemo4f490bc5_userpool_4f490bc5", 6 | "autoVerifiedAttributes": [ 7 | "email" 8 | ], 9 | "mfaConfiguration": "OFF", 10 | "mfaTypes": [ 11 | "SMS Text Message" 12 | ], 13 | "smsAuthenticationMessage": "Your authentication code is {####}", 14 | "smsVerificationMessage": "Your verification code is {####}", 15 | "emailVerificationSubject": "Your verification code", 16 | "emailVerificationMessage": "Your verification code is {####}", 17 | "defaultPasswordPolicy": false, 18 | "passwordPolicyMinLength": 8, 19 | "passwordPolicyCharacters": [], 20 | "requiredAttributes": [ 21 | "email" 22 | ], 23 | "userpoolClientGenerateSecret": false, 24 | "userpoolClientRefreshTokenValidity": 30, 25 | "userpoolClientWriteAttributes": [ 26 | "email" 27 | ], 28 | "userpoolClientReadAttributes": [ 29 | "email" 30 | ], 31 | "userpoolClientLambdaRole": "dlwamp4f490bc5_userpoolclient_lambda_role", 32 | "userpoolClientSetAttributes": false, 33 | "sharedId": "4f490bc5", 34 | "resourceName": "dlwamplifyauthdemo4f490bc5", 35 | "authSelections": "identityPoolAndUserPool", 36 | "authRoleArn": { 37 | "Fn::GetAtt": [ 38 | "AuthRole", 39 | "Arn" 40 | ] 41 | }, 42 | "unauthRoleArn": { 43 | "Fn::GetAtt": [ 44 | "UnauthRole", 45 | "Arn" 46 | ] 47 | }, 48 | "useDefault": "default", 49 | "userPoolGroupList": [], 50 | "serviceName": "Cognito", 51 | "usernameCaseSensitive": false, 52 | "dependsOn": [] 53 | } -------------------------------------------------------------------------------- /amplify/backend/backend-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "auth": { 3 | "dlwamplifyauthdemo4f490bc5": { 4 | "service": "Cognito", 5 | "providerPlugin": "awscloudformation", 6 | "dependsOn": [], 7 | "customAuth": false 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /amplify/backend/tags.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Key": "user:Stack", 4 | "Value": "{project-env}" 5 | }, 6 | { 7 | "Key": "user:Application", 8 | "Value": "{project-name}" 9 | } 10 | ] -------------------------------------------------------------------------------- /amplify/cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "features": { 3 | "graphqltransformer": { 4 | "addmissingownerfields": true, 5 | "validatetypenamereservedwords": true, 6 | "useexperimentalpipelinedtransformer": false, 7 | "enableiterativegsiupdates": false, 8 | "secondarykeyasgsi": true 9 | }, 10 | "frontend-ios": { 11 | "enablexcodeintegration": true 12 | }, 13 | "auth": { 14 | "enablecaseinsensitivity": true 15 | }, 16 | "codegen": { 17 | "useappsyncmodelgenplugin": true, 18 | "usedocsgeneratorplugin": true, 19 | "usetypesgeneratorplugin": true 20 | }, 21 | "appsync": { 22 | "generategraphqlpermissions": true 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /amplify/team-provider-info.json: -------------------------------------------------------------------------------- 1 | { 2 | "dev": { 3 | "awscloudformation": { 4 | "AuthRoleName": "amplify-dlwamplifyauthdemo-dev-192511-authRole", 5 | "UnauthRoleArn": "arn:aws:iam::191324695802:role/amplify-dlwamplifyauthdemo-dev-192511-unauthRole", 6 | "AuthRoleArn": "arn:aws:iam::191324695802:role/amplify-dlwamplifyauthdemo-dev-192511-authRole", 7 | "Region": "eu-west-2", 8 | "DeploymentBucketName": "amplify-dlwamplifyauthdemo-dev-192511-deployment", 9 | "UnauthRoleName": "amplify-dlwamplifyauthdemo-dev-192511-unauthRole", 10 | "StackName": "amplify-dlwamplifyauthdemo-dev-192511", 11 | "StackId": "arn:aws:cloudformation:eu-west-2:191324695802:stack/amplify-dlwamplifyauthdemo-dev-192511/297eeb80-85c4-11eb-9204-0274299c8dfc", 12 | "AmplifyAppId": "d38uis4h7i8yib" 13 | }, 14 | "categories": { 15 | "auth": { 16 | "dlwamplifyauthdemo4f490bc5": {} 17 | } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-authentication", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start" 9 | }, 10 | "dependencies": { 11 | "@aws-amplify/ui-react": "^1.0.3", 12 | "@tailwindcss/forms": "^0.2.1", 13 | "aws-amplify": "^3.3.22", 14 | "next": "10.0.7", 15 | "react": "17.0.1", 16 | "react-dom": "17.0.1", 17 | "react-hook-form": "^6.15.4" 18 | }, 19 | "devDependencies": { 20 | "autoprefixer": "^10.2.4", 21 | "postcss": "^8.2.7", 22 | "tailwindcss": "^2.0.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwhiteGUK/dlw-custom-auth-ui-nextjs-amplify/662fa10d2107ee39e924bb05c3238a9e11c9b432/public/favicon.ico -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/confirm.js: -------------------------------------------------------------------------------- 1 | import { Auth } from 'aws-amplify'; 2 | import { useForm } from "react-hook-form"; 3 | import { useRouter } from 'next/router' 4 | export default function Register({ user, setUser }) { 5 | const { register, handleSubmit } = useForm(); 6 | const router = useRouter() 7 | 8 | async function confirmSignUp({ code }) { 9 | try { 10 | await Auth.confirmSignUp(user.username, code); 11 | 12 | await Auth.signIn(user.username, user.password); 13 | 14 | setUser(null) 15 | 16 | router.push('/client-protected') 17 | } catch (error) { 18 | console.log('error confirming sign up', error); 19 | } 20 | } 21 | 22 | return ( 23 |
24 | 25 |
26 |
27 | 28 | 29 |
30 |
31 | 32 |
33 | 41 |
42 |
43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /src/components/register.js: -------------------------------------------------------------------------------- 1 | import { Auth } from 'aws-amplify'; 2 | import { useForm } from "react-hook-form"; 3 | export default function Register({ setStatus, setUser }) { 4 | const { register, handleSubmit } = useForm(); 5 | 6 | async function signUp({ email, username, password }) { 7 | try { 8 | await Auth.signUp({ 9 | username, 10 | password, 11 | attributes: { 12 | email, // optional but not in this case as MFA/Verification code wil be emailed 13 | } 14 | }); 15 | 16 | setStatus('confirm') 17 | setUser({ 18 | username: username, 19 | password: password, 20 | }) 21 | 22 | } catch (error) { 23 | console.log('error signing up:', error); 24 | } 25 | } 26 | 27 | return ( 28 |
29 | 30 |
31 |
32 | 33 | 34 |
35 |
36 | 37 | 38 |
39 |
40 | 41 | 42 |
43 |
44 | 45 |
46 |
47 | 53 |
54 |
55 | 56 |
57 | 65 |
66 |
67 | ) 68 | } 69 | -------------------------------------------------------------------------------- /src/components/sign-in.js: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router' 2 | import { Auth } from 'aws-amplify'; 3 | import { useForm } from "react-hook-form"; 4 | export default function SignIn({ setStatus }) { 5 | const { register, handleSubmit } = useForm(); 6 | const router = useRouter() 7 | 8 | async function signIn({ username, password }) { 9 | try { 10 | await Auth.signIn(username, password); 11 | 12 | router.push('/client-protected') 13 | } catch (error) { 14 | console.log('error signing in', error); 15 | } 16 | } 17 | 18 | return ( 19 | 20 |
21 | 22 |
23 |
24 | 25 | 26 |
27 |
28 | 29 | 30 |
31 |
32 | 33 |
34 |
35 | 41 |
42 |
43 | 44 |
45 | 53 |
54 |
55 | ) 56 | } 57 | -------------------------------------------------------------------------------- /src/components/sign-out.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import { useRouter } from 'next/router' 3 | import { Auth } from 'aws-amplify'; 4 | export default function SignIn() { 5 | const router = useRouter() 6 | 7 | async function signOut() { 8 | try { 9 | await Auth.signOut(); 10 | router.push('/') 11 | } catch (error) { 12 | console.log('error signing out: ', error); 13 | } 14 | } 15 | 16 | return ( 17 | 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /src/pages/_app.js: -------------------------------------------------------------------------------- 1 | import Amplify from 'aws-amplify' 2 | 3 | import "tailwindcss/tailwind.css"; 4 | import config from '../aws-exports' 5 | 6 | Amplify.configure({ 7 | ...config, 8 | ssr: true, 9 | }) 10 | 11 | function MyApp({ Component, pageProps }) { 12 | return 13 | } 14 | 15 | export default MyApp 16 | -------------------------------------------------------------------------------- /src/pages/api/hello.js: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | 3 | export default (req, res) => { 4 | res.status(200).json({ name: 'John Doe' }) 5 | } 6 | -------------------------------------------------------------------------------- /src/pages/client-protected.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react' 2 | import { Auth } from 'aws-amplify' 3 | import { withAuthenticator } from '@aws-amplify/ui-react' 4 | import Head from 'next/head' 5 | 6 | import SignOut from '../components/sign-out' 7 | 8 | function ClientProtected() { 9 | const [user, setUser] = useState(null) 10 | 11 | useEffect(() => { 12 | Auth.currentAuthenticatedUser() 13 | .then(user => { 14 | setUser(user) 15 | }) 16 | .catch(err => setUser(null)) 17 | }, []) 18 | 19 | return ( 20 |
21 | 22 | Authentication with Amplify, React Hook form and Tailwind CSS 23 | 24 | 25 |
26 |
27 |
28 |
29 | Workflow 30 | {user && ( 31 | <> 32 |

33 | Welcome, {user.username} 34 |

35 | 36 | 37 | )} 38 |
39 |
40 |
41 |
42 |
43 | ) 44 | } 45 | 46 | export default withAuthenticator(ClientProtected) 47 | -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import Head from 'next/head' 3 | 4 | import Register from '../components/register' 5 | import SignIn from '../components/sign-in' 6 | import Confirm from '../components/confirm' 7 | 8 | export default function Home() { 9 | const [status, setStatus] = useState('sign-in') 10 | const [user, setUser] = useState(null) 11 | 12 | return ( 13 |
14 | 15 | Authentication with Amplify, React Hook form and Tailwind CSS 16 | 17 | 18 |
X 19 |
20 |
21 |
22 | Workflow 23 |

24 | Example Amplify Register 25 |

26 |
27 | 28 | {status === 'sign-in' ? : null} 29 | {status === 'register' ? : null} 30 | {status === 'confirm' ? : null} 31 |
32 |
33 |
34 |
35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /src/pages/server-protected.js: -------------------------------------------------------------------------------- 1 | import { withSSRContext } from 'aws-amplify' 2 | 3 | function ServerProtected({ authenticated, username }) { 4 | if (!authenticated) { 5 | return

Not authenticated

6 | } 7 | 8 | return

{username}, SSR route

9 | } 10 | 11 | export async function getServerSideProps(context) { 12 | const { Auth } = await withSSRContext(context) 13 | 14 | try { 15 | const user = await Auth.currentAuthenticatedUser() 16 | return { 17 | props: { 18 | authenticated: true, 19 | username: user.username 20 | } 21 | } 22 | } catch (error) { 23 | return { 24 | props: { 25 | authenticated: false 26 | } 27 | } 28 | } 29 | } 30 | 31 | export default ServerProtected 32 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'], 3 | darkMode: false, // or 'media' or 'class' 4 | theme: { 5 | extend: {}, 6 | }, 7 | variants: { 8 | extend: {}, 9 | }, 10 | plugins: [ 11 | require('@tailwindcss/forms'), 12 | ] 13 | } 14 | --------------------------------------------------------------------------------