1 of 25

Cognito, Lambda & S3

A complete serverless member portal solution

2 of 25

✧ Who am I?

✧ The problem

✧ The solution

✧ The details

✧ The future

✧ Questions?

3 of 25

I’m a software architect and developer by heart

Michael Dürgner

35 years, German

Engineering Lead @ Zalando SE

@duergner

duergner

duergner

4 of 25

✧ 15 countries

✧ 5 Tech locations

✧ 6 warehouse locations

✧ ~3.6B in revenue (2016)

✧ ~200M visits / month

We’re hiring!

5 of 25

The problem

6 of 25

✧ Perl “WebApp”

✧ Based on custom Framework

✧ Requires very old Perl

✧ Breaks every now and then

✧ Server maintenance

7 of 25

Low maintenance is a key non-functional requirement

Non-functional requirements

Sign-ups need approval

Audit-trail for changes

Low maintenance effort

Functional requirements

Sign-in with various providers

Update personal data

Share personal profile

Browse event photos

8 of 25

The solution

9 of 25

AWS Simple Storage Service

✧ Easy to use

✧ Security and Access Mgmt.

✧ Data Durability

✧ Scalability

✧ Well known :-)

10 of 25

AWS Lambda

✧ Build custom backends

✧ Automated administration

✧ Built-in fault tolerance

✧ Integrated security model

✧ Bring your own code

11 of 25

AWS Identity & Access Mgmt.

✧ Enhanced security

✧ Granular control

✧ Seamless integration

✧ External identity systems

✧ Temporary credentials

12 of 25

AWS Cognito

✧ Social identity

✧ Custom user pools

✧ Federated identities

✧ Synchronize data

✧ Web and native mobile

13 of 25

Build a single page app based on AWS technology

Authentication and Authorization via AWS Cognito

Web app stored on AWS S3

Delivered via AWS CloudFront

ReactJS & Redux web app

Login with Google, Facebook, Twitter and email

web app

S3

Cognito

Google

Facebook

IAM

14 of 25

Access is done fully based on IAM roles and policies

Cognito default Auth and Unauth roles

Auth role only allows for “sign up” by default

Once approved additional operations are permitted

UnAuth

WriteOwnProfileData

ReadAllProfileData

ReadAllPhotoData

Auth

WriteOwnAccountData

Cognito IDs

15 of 25

The details

16 of 25

Restricting unapproved access is key to prevent abuse

AuthRole allows only to write sign up information

Read rights are added after approval

{� "Action": [� "s3:GetObject"� ],� "Effect": "Allow",� "Resource": [� "arn:aws:s3:::my-bucket/accounts/${cognito-identity.amazonaws.com:sub}/base.json"� ],

"Condition": {

"StringLike": {

"cognito-identity.amazonaws.com:aud": "eu-central-1:UUID",

"cognito-identity.amazonaws.com:sub": [ "eu-central-1:UUID" ]

}

}�}

{� "Action": [� "s3:DeleteObject",� "s3:PutObject",� "s3:HeadObject"� ],� "Effect": "Allow",� "Resource": [� "arn:aws:s3:::my-bucket/accounts/${cognito-identity.amazonaws.com:sub}/base.json"� ]�}

17 of 25

Lambda function for approving a member

const AWS = require('aws-sdk');

exports.handler = (event, context, callback) => {

const iam = new AWS.IAM();

const cognitoId = event.cognito_id;

iam.getRolePolicy(getRolePolicyParams, (err, data) => {

if (err) callback(err);

else {

const policy = JSON.parse(decodeURIComponent(data.PolicyDocument));

policy.Statement = policy.Statement.map((statement) => {

if (statement.Condition.StringLike['cognito-identity.amazonaws.com:sub'].indexOf(cognitoId) == -1) {

statement.Condition.StringLike['cognito-identity.amazonaws.com:sub'].push(cognitoId);

}

return statement;

});

iam.putRolePolicy(putRolePolicyParams, (err, data) => {

if (err) callback(err);

else callback(null, { message: 'Member approved' });

});

}

});

};

18 of 25

Simplified ReactJS profile editing component

export const ProfileEditComponent = ({ name, number, email, links, update }) => {

return (

<div className="container">

<div className="row">

<div className="input-field col s12">

<input placeholder="Name" id="name" type="text" className="validate" value={ name } onChange={ update(name) } />

<label htmlFor="name">Name</label>

</div>

</div>

<div className="row">

<div className="input-field col s12">

<input placeholder="Number" id="number" type="text" className="validate" value={ number } onChange={ update(number) } />

<label htmlFor="number">Number</label>

</div>

</div>

{ links.map((link) => <LinkEditComponent link={ link } update={ update } />) }

</div>

);

};

19 of 25

Redux dispatch mapping and simplified update action

const mapDispatchToProps = (dispatch) => {

return {

update: (field) => (e) => dispatch(updateProfileField(field, e.target.val()))

}

};

export const updateProfileField = (field, newValue) => {

return (dispatch, getState) => {

const { profile: { data } } = getState();

return new Promise((resolve, reject) => {

data[field] = newValue;

AWS.config.credentials.refresh(() => {

const { webIdentityCredentials: { params: { IdentityId: sub } } } = AWS.config.credentials;

const S3 = new AWS.S3();

const Key = 'profiles/' + sub + '/private.json';

S3.putObject(

{Bucket: BUCKET_NAME, Key: Key, ContentType: 'application/json', Body: JSON.stringify(data)},

(err) => err ? reject(err) : resolve(data));

});

});

}

};

20 of 25

Stored JSON data in S3

21 of 25

Viewing a public profile on the client

export const loadProfile = (id) => {

return (dispatch) => {

dispatch({type: LOAD_PUBLIC_PROFILE_REQUEST, profile: id });

return new Promise((resolve, reject) => {

AWS.config.credentials.refresh(() => {

const { webIdentityCredentials: { params: { IdentityId: sub } } } = AWS.config.credentials;

new AWS.S3().getObject(

{Bucket: BUCKET_NAME, Key: 'profiles/' + id + '/public.json'},

(err, data) => {

if (err) {

dispatch({type: LOAD_PUBLIC_PROFILE_FAILURE, profile: id});

reject(err);

} else {

const profile = JSON.parse(new TextDecoder('UTF8').decode(data.Body));

dispatch({type: LOAD_PUBLIC_PROFILE_SUCCESS, profile: profile});

resolve(profile)

}

});

});

});

};

};

22 of 25

S3 beats DynamoDB due to data access possibilities

Plain text JSON files

Can be synced locally with AWS CLI tools

Easy to manipulate and transform into e.g. CSV

S3 was required for photo storage anyway

23 of 25

The future

24 of 25

Extend using Lambda functions and social logins

Update profile via Lambda functions to enforce constraints

Photo management section incl. albums, various sizes, etc.

Event management section incl. signup and link to photos

Add additional social logins (Twitter, Google, etc.)

Integrate social sharing functions

25 of 25

michael.duergner@gmail.com

✧ Code on GitHub (soon)

Blog posts coming