Skip to content

Commit c99c4d2

Browse files
author
Ahmed Hamouda
committed
feat: add passwordless authentication support
1 parent 34dc06f commit c99c4d2

File tree

17 files changed

+1565
-11
lines changed

17 files changed

+1565
-11
lines changed

.changeset/wise-crews-think.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@aws-amplify/integration-tests': minor
3+
'@aws-amplify/auth-construct': minor
4+
'@aws-amplify/backend-auth': minor
5+
'@aws-amplify/backend': minor
6+
---
7+
8+
Added support for passwordless authentication

.eslint_dictionary.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"aggregator",
77
"amazonaws",
88
"amazoncognito",
9+
"amplifyapp",
910
"amplifyconfiguration",
1011
"ampx",
1112
"anonymize",
@@ -18,8 +19,10 @@
1819
"argv",
1920
"arn",
2021
"arns",
22+
"authn",
2123
"aws",
2224
"backends",
25+
"biometric",
2326
"birthdate",
2427
"bundler",
2528
"callee",
@@ -140,6 +143,7 @@
140143
"orchestrator",
141144
"outdir",
142145
"passthrough",
146+
"passwordless",
143147
"pathname",
144148
"pipelined",
145149
"pnpm",
@@ -217,6 +221,7 @@
217221
"verifier",
218222
"versioned",
219223
"versioning",
224+
"webauthn",
220225
"whoami",
221226
"wildcard",
222227
"wildcards",

package-lock.json

Lines changed: 12 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/auth-construct/API.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export type AuthProps = {
4545
loginWith: {
4646
email?: EmailLogin;
4747
phone?: PhoneNumberLogin;
48+
webAuthn?: WebAuthnLogin;
4849
externalProviders?: ExternalProviderOptions;
4950
};
5051
senders?: {
@@ -108,6 +109,7 @@ export type EmailLoginSettings = (VerificationEmailWithLink | VerificationEmailW
108109
emailBody?: (username: () => string, code: () => string) => string;
109110
smsMessage?: (username: () => string, code: () => string) => string;
110111
};
112+
otpLogin?: boolean;
111113
};
112114

113115
// @public
@@ -178,6 +180,7 @@ export type OidcProviderProps = Omit<aws_cognito.UserPoolIdentityProviderOidcPro
178180
// @public
179181
export type PhoneNumberLogin = true | {
180182
verificationMessage?: (createCode: () => string) => string;
183+
otpLogin?: boolean;
181184
};
182185

183186
// @public
@@ -217,6 +220,15 @@ export type VerificationEmailWithLink = {
217220
verificationEmailSubject?: string;
218221
};
219222

223+
// @public
224+
export type WebAuthnLogin = true | WebAuthnOptions;
225+
226+
// @public
227+
export type WebAuthnOptions = {
228+
relyingPartyId: string;
229+
userVerification?: 'required' | 'preferred';
230+
};
231+
220232
// (No @packageDocumentation comment for this package)
221233

222234
```

packages/auth-construct/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"dependencies": {
2626
"@aws-amplify/backend-output-schemas": "^1.7.1",
2727
"@aws-amplify/backend-output-storage": "^1.3.2",
28+
"@aws-amplify/platform-core": "^1.10.2",
2829
"@aws-amplify/plugin-types": "^1.11.1",
2930
"@aws-sdk/util-arn-parser": "^3.723.0"
3031
},

packages/auth-construct/src/construct.test.ts

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3144,4 +3144,190 @@ void describe('Auth construct', () => {
31443144
UserPoolName: Match.absent(),
31453145
});
31463146
});
3147+
3148+
void describe('passwordless authentication', () => {
3149+
void it('configures email OTP when otpLogin is enabled', () => {
3150+
const app = new App();
3151+
const stack = new Stack(app);
3152+
new AmplifyAuth(stack, 'test', {
3153+
loginWith: {
3154+
email: {
3155+
otpLogin: true,
3156+
},
3157+
},
3158+
});
3159+
const template = Template.fromStack(stack);
3160+
template.hasResourceProperties('AWS::Cognito::UserPool', {
3161+
Policies: {
3162+
SignInPolicy: {
3163+
AllowedFirstAuthFactors: ['PASSWORD', 'EMAIL_OTP'],
3164+
},
3165+
},
3166+
});
3167+
template.hasResourceProperties('AWS::Cognito::UserPoolClient', {
3168+
ExplicitAuthFlows: Match.arrayWith(['ALLOW_USER_AUTH']),
3169+
});
3170+
});
3171+
3172+
void it('configures SMS OTP when otpLogin is enabled', () => {
3173+
const app = new App();
3174+
const stack = new Stack(app);
3175+
new AmplifyAuth(stack, 'test', {
3176+
loginWith: {
3177+
phone: {
3178+
otpLogin: true,
3179+
},
3180+
},
3181+
});
3182+
const template = Template.fromStack(stack);
3183+
template.hasResourceProperties('AWS::Cognito::UserPool', {
3184+
Policies: {
3185+
SignInPolicy: {
3186+
AllowedFirstAuthFactors: ['PASSWORD', 'SMS_OTP'],
3187+
},
3188+
},
3189+
});
3190+
template.hasResourceProperties('AWS::Cognito::UserPoolClient', {
3191+
ExplicitAuthFlows: Match.arrayWith(['ALLOW_USER_AUTH']),
3192+
});
3193+
});
3194+
3195+
void it('configures WebAuthn with default settings', () => {
3196+
const app = new App();
3197+
const stack = new Stack(app);
3198+
stack.node.setContext('amplify-backend-type', 'sandbox');
3199+
new AmplifyAuth(stack, 'test', {
3200+
loginWith: {
3201+
email: true,
3202+
webAuthn: true,
3203+
},
3204+
});
3205+
const template = Template.fromStack(stack);
3206+
template.hasResourceProperties('AWS::Cognito::UserPool', {
3207+
Policies: {
3208+
SignInPolicy: {
3209+
AllowedFirstAuthFactors: ['PASSWORD', 'WEB_AUTHN'],
3210+
},
3211+
},
3212+
WebAuthnRelyingPartyID: 'localhost',
3213+
WebAuthnUserVerification: 'preferred',
3214+
});
3215+
template.hasResourceProperties('AWS::Cognito::UserPoolClient', {
3216+
ExplicitAuthFlows: Match.arrayWith(['ALLOW_USER_AUTH']),
3217+
});
3218+
});
3219+
3220+
void it('configures WebAuthn with custom settings', () => {
3221+
const app = new App();
3222+
const stack = new Stack(app);
3223+
new AmplifyAuth(stack, 'test', {
3224+
loginWith: {
3225+
email: true,
3226+
webAuthn: {
3227+
relyingPartyId: 'example.com',
3228+
userVerification: 'required',
3229+
},
3230+
},
3231+
});
3232+
const template = Template.fromStack(stack);
3233+
template.hasResourceProperties('AWS::Cognito::UserPool', {
3234+
Policies: {
3235+
SignInPolicy: {
3236+
AllowedFirstAuthFactors: ['PASSWORD', 'WEB_AUTHN'],
3237+
},
3238+
},
3239+
WebAuthnRelyingPartyID: 'example.com',
3240+
WebAuthnUserVerification: 'required',
3241+
});
3242+
});
3243+
3244+
void it('configures all passwordless factors together', () => {
3245+
const app = new App();
3246+
const stack = new Stack(app);
3247+
new AmplifyAuth(stack, 'test', {
3248+
loginWith: {
3249+
email: {
3250+
otpLogin: true,
3251+
},
3252+
phone: {
3253+
otpLogin: true,
3254+
},
3255+
webAuthn: {
3256+
relyingPartyId: 'example.com',
3257+
},
3258+
},
3259+
});
3260+
const template = Template.fromStack(stack);
3261+
template.hasResourceProperties('AWS::Cognito::UserPool', {
3262+
Policies: {
3263+
SignInPolicy: {
3264+
AllowedFirstAuthFactors: [
3265+
'PASSWORD',
3266+
'EMAIL_OTP',
3267+
'SMS_OTP',
3268+
'WEB_AUTHN',
3269+
],
3270+
},
3271+
},
3272+
WebAuthnRelyingPartyID: 'example.com',
3273+
WebAuthnUserVerification: 'preferred',
3274+
});
3275+
template.hasResourceProperties('AWS::Cognito::UserPoolClient', {
3276+
ExplicitAuthFlows: Match.arrayWith(['ALLOW_USER_AUTH']),
3277+
});
3278+
});
3279+
3280+
void it('resolves AUTO to localhost in sandbox mode', () => {
3281+
const app = new App();
3282+
const stack = new Stack(app);
3283+
stack.node.setContext('amplify-backend-type', 'sandbox');
3284+
new AmplifyAuth(stack, 'test', {
3285+
loginWith: {
3286+
email: true,
3287+
webAuthn: true,
3288+
},
3289+
});
3290+
const template = Template.fromStack(stack);
3291+
template.hasResourceProperties('AWS::Cognito::UserPool', {
3292+
WebAuthnRelyingPartyID: 'localhost',
3293+
});
3294+
});
3295+
3296+
void it('resolves AUTO to Amplify domain in branch mode', () => {
3297+
const app = new App();
3298+
const stack = new Stack(app);
3299+
stack.node.setContext('amplify-backend-type', 'branch');
3300+
stack.node.setContext('amplify-backend-namespace', 'testProjectName');
3301+
stack.node.setContext('amplify-backend-name', 'main');
3302+
new AmplifyAuth(stack, 'test', {
3303+
loginWith: {
3304+
email: true,
3305+
webAuthn: true,
3306+
},
3307+
});
3308+
const template = Template.fromStack(stack);
3309+
template.hasResourceProperties('AWS::Cognito::UserPool', {
3310+
WebAuthnRelyingPartyID: 'main.testProjectName.amplifyapp.com',
3311+
});
3312+
});
3313+
3314+
void it('does not configure passwordless when not enabled', () => {
3315+
const app = new App();
3316+
const stack = new Stack(app);
3317+
new AmplifyAuth(stack, 'test', {
3318+
loginWith: {
3319+
email: true,
3320+
},
3321+
});
3322+
const template = Template.fromStack(stack);
3323+
template.hasResourceProperties('AWS::Cognito::UserPool', {
3324+
Policies: {
3325+
PasswordPolicy: Match.objectLike({}),
3326+
SignInPolicy: Match.absent(),
3327+
},
3328+
WebAuthnRelyingPartyID: Match.absent(),
3329+
WebAuthnUserVerification: Match.absent(),
3330+
});
3331+
});
3332+
});
31473333
});

0 commit comments

Comments
 (0)