Πώς να ασφαλίσετε Μικροσυσκευές σε AWS με Cognito, API Gateway και Lambda

Ο χειρισμός της ταυτότητας είναι επώδυνη. Ωστόσο, οι περισσότερες εφαρμογές πρέπει να ελέγχουν τους χρήστες και να ελέγχουν τους πόρους στους οποίους μπορούν να έχουν πρόσβαση. Οι μικροϋπηρεσίες, αν και αυξάνονται σε δημοτικότητα, μπορούν να προσθέσουν πολυπλοκότητα. Πρέπει να διασφαλίσετε τόσο τις ενέργειες του χρήστη όσο και τις αλληλεπιδράσεις μεταξύ των υπηρεσιών.

Το AWS προσφέρει μερικά μεγάλα δομικά στοιχεία για την αρχιτεκτονική των μικροϋπηρεσιών. Αλλά όπως τα έπιπλα από το ΙΚΕΑ, πρέπει να συγκεντρώσετε τα κομμάτια μόνοι σας. Επιπλέον, οι οδηγίες δεν είναι πολύ καλές.

Θα δημιουργήσουμε μια απλή εφαρμογή και θα διαμορφώσουμε το AWS για έλεγχο ταυτότητας ενός χρήστη και θα διασφαλίσουμε μια μικροϋπηρεσία.

TL; DR (για τον ανυπόμονο)

Επίδειξη εργασίας: //auth-api-demo.firebaseapp.com/ (χρήστης: demouserκωδικός πρόσβασης:demoPASS123)

Repo GitHub : //github.com/csepulv/auth-api-demo

Περίπτωση βασικής χρήσης / υπόθεση: Υπάρχουν δύο ομάδες πόρων - α) εκείνοι που χρειάζονται έναν πιστοποιημένο χρήστη και β) εκείνοι που δεν το κάνουν.

Θα χρησιμοποιήσουμε

  • AWS Lambda, API Gateway και Cognito
  • Claudia.js (για τη δημιουργία του API μας)
  • Αντιδράστε (για τον πελάτη Ιστού μας)

Για όσους διαβάζουν μέχρι το τέλος, υπάρχουν μερικά καλούδια.

Τώρα, για τις λεπτομέρειες.

Εννοιολογικό μοντέλο εφαρμογής

Η εφαρμογή επίδειξης εφαρμόζει το ακόλουθο μοντέλο.

  • Ένας χρήστης συνδέεται σε μια εφαρμογή και λαμβάνει ένα διακριτικό ελέγχου ταυτότητας
  • Το AWS χρησιμοποιεί αυτό το διακριτικό για να επαληθεύσει την ταυτότητα και να εγκρίνει αιτήματα χρηστών για προστατευμένους πόρους
  • το App Gateway δημιουργεί μια εικονική τάφρο μεταξύ χρηστών και πόρων εφαρμογής

Υπηρεσίες AWS

Εάν είστε νέοι στο AWS, υπάρχει η επίσημη πύλη έναρξης AWS. Επίσης, ο Udemy έχει ένα δωρεάν μάθημα, AWS Essentials.

Θα χρειαστείτε πρόσβαση σε έναν λογαριασμό AWS. Μπορείτε να εγγραφείτε στη δωρεάν κατηγορία AWS.

AWS Λάμδα

Ενώ το EC2 είναι μια από τις πιο δημοφιλείς επιλογές AWS, νομίζω ότι το Lambda είναι πιο κατάλληλο για μικροσυσκευές. Οι παρουσίες EC2 είναι εικονικές μηχανές. Είστε υπεύθυνοι για τα πάντα, από το λειτουργικό σύστημα έως όλο το λογισμικό που εκτελεί. Το Lambda είναι μια λειτουργία ως μοντέλο υπηρεσίας. Δεν υπάρχει παροχή ή ανάπτυξη διακομιστή. γράφετε τη λογική της υπηρεσίας σας.

Για περισσότερες πληροφορίες, ανατρέξτε στα έγγραφα AWS Lambda.

Αλλά υπάρχει μια ρυτίδα με τον Λάμπδα. Δεν μπορούν να προσεγγιστούν απευθείας από έναν χρήστη της εφαρμογής. Το Lambdas χρειάζεται ενεργοποιητές που επικαλούνται τη λειτουργία Lambda. Αυτό μπορεί να είναι ένα μήνυμα σε ουρά ή, στην περίπτωσή μας, ένα αίτημα πύλης API.

Πύλη API AWS

Μια πύλη API παρέχει τάφρο γύρω από τις υπηρεσίες εφαρμογής σας. Μπορεί να καταγράφει τη δραστηριότητα των χρηστών, να ελέγχει τα αιτήματα και να επιβάλλει πολιτικές χρήσης (όπως περιορισμός τιμών) (Τα έγγραφα AWS API Gateway είναι μια καλή αναφορά.)

AWS Cognito

Το AWS Cognito είναι μια υπηρεσία διαχείρισης χρηστών, ελέγχου ταυτότητας και ελέγχου πρόσβασης. Δυστυχώς, όλες οι δυνατότητες και η διαμόρφωση μπορεί να προκαλούν σύγχυση κατά καιρούς. (Λες και η ασφάλεια και ο έλεγχος ταυτότητας ήταν πάντα εύκολο.;) Θα επικεντρωθούμε στα βασικά στοιχεία του Cognito για την ασφάλεια του API μας.

Εφαρμογή και ρύθμιση περιβάλλοντος

Η συνταγή για την εφαρμογή επίδειξης είναι:

  1. Στο AWS Cognito, δημιουργήστε μια ομάδα χρηστών (με μια εφαρμογή πελάτη) και μια ομαδοποιημένη ομάδα ταυτότητας.
  2. Στο AWS API Gateway, δημιουργήστε ένα σχέδιο χρήσης και ένα κλειδί API
  3. Χρησιμοποιώντας το Claudia JS, δημιουργήστε και αναπτύξτε ένα απλό API βασισμένο σε AWS Lambda.
  4. Ενημερώστε το ρόλο AWS IAM για να παραχωρήσετε στους πιστοποιημένους χρήστες πρόσβαση σε προστατευμένες μεθόδους API
  5. Δημιουργήστε μια εφαρμογή μιας σελίδας (SPA) χρησιμοποιώντας create-react-app. Θα χρησιμοποιεί AWS Cognito και υποβάλλει αιτήματα API με υπογραφή (και έλεγχο ταυτότητας)

Η λεπτομερής ρύθμιση AWS βρίσκεται στο aws-setup.mddemo GitHub repo. Θα επισημάνουμε τις πτυχές της εγκατάστασης και θα εξηγήσουμε ότι τα πράγματα λειτουργούν.

AWS Cognito

Ομάδα χρηστών, εφαρμογή πελάτη και όνομα τομέα

Θα δημιουργήσουμε μια ομάδα χρηστών με τις προεπιλογές. Λεπτομέρειες και στιγμιότυπα οθόνης:

  • Πισίνα χρηστών
  • Εφαρμογή πελάτη
  • Ονομα τομέα

Συγκεντρωτική ομάδα ταυτότητας

Μπορεί να είναι λίγο συγκεχυμένο που χρειαζόμαστε τόσο μια ομάδα χρηστών όσο και μια ομαδοποιημένη ομάδα ταυτότητας Ο Ashan Fernando έχει μια αρκετά καλή εξήγηση σε αυτήν την ανάρτηση. Θεσω απλα,

  • Οι ομάδες χρηστών παρέχουν πρόσβαση για έναν χρήστη σε μια εφαρμογή. Αυτό είναι σαν υπηρεσίες όπως το Auth0.
  • Το Federated Identity Pool παρέχει πρόσβαση σε πόρους AWS.

By combining the two pools, our application can authenticate a user and AWS will assign temporary credentials. These credentials allow the user to access AWS resources. The IAM role, configured in the Identity Pool, specifies the privileges for the temporary credentials.

The detailed Federated Identity Pool setup is here.

AWS API Gateway

I suggest creating a usage plan for our API. While not a requirement, it is a good practice, as AWS costs can “run away” if you aren’t careful. We will create a Usage Plan, named api-auth-demo and set a throttle and burst rate, and a daily quota for API calls. We will also create an API key, which the web client application will use. (Full setup details are here.)

We’ve finished the bulk of our AWS setup. We will now write our Lambda functions and then build our React web application.

AWS Lambda and Claudia JS

We will write our Lambda functions using Node.js. Claudia.js will deploy our Lambdas and configure the API Gateway. (As a note, the Serverless framework provides similar functionality.)

We only need a simple API for our example. We’ll create two API methods (i.e. very simple microservices): one for authenticated users and one for guests.

We’ll use the Claudia API Builder, which lets multiple routes map to a single lambda. The routing mechanism is similar to routing in frameworks such as Express.js.

 const ApiBuilder = require("claudia-api-builder"); const api = new ApiBuilder(); api.get("/no-auth",request => { return {message: "Open for All!"}; }, { apiKeyRequired: true } ); api.get("/require-auth", request => { return {message: "You're past the velvet rope!"}; }, { apiKeyRequired: true, authorizationType: "AWS_IAM" } ); module.exports = api; view rawapi.js hosted with ❤ by GitHub

We’ll use the Claudia.js command line to deploy the API to AWS.

claudia create --region us-west-2 --api-module api --name auth-api-demo

NOTE: Any changes to api.js will need to be re-deployed. Useclaudia update...

API Keys and Auth

In api.js, {apiKeyRequired: true} indicates that API requests require an API key. {authorizationType: 'AWS_IAM'} configures the API Gateway to authorize using AWS IAM. The underlying authentication mechanism is not obvious. The AWS docs outline the approach, but a summary is:

  • when a user signs in, Cognito will issue tokens for temporary credentials (obtained via STS).
  • for protected resources, the application needs to sign requests using these credentials
  • AWS decodes and verifies the signature
  • if the signature is valid, the API Gateway dispatches the request

There are other authorization methods available. The Claudia.js docs outline how to specify other methods. (The corresponding AWS docs are here.)

AWS IAM Roles for Authenticated Users

We need to edit the privileges for the IAM roles for authenticated users. We need to allow invoking the API Gateway method we created.

We need the ARN of the API Gateway. Go to the API Gateway console and find the API Gateway resource/method.

  • Copy the ARN
  • Go to the IAM console and find the Authenticated role created during the Cognito Federated Identity Pool setup
  • add an Inline Policy as below
  • Specify the copied ARN for the API Gateway resource in the policy.

Authenticated users can now invoke our protected API methods.

Service to Service Access Control

The Cognito setup will allow a user to invoke an API method. But this method invocation is a trigger for a Lambda function. The Lambda function executes within the context of a different IAM role. It is no longer a direct user request, but an AWS service to service interaction. IAM roles provide access control for this interaction.

Claudia.JS created the IAM role for the Lambda function. (You can also manually create this role and specify its identifier to Claudia.JS via the --role parameter. Details are here.)

If our Lambda function needs access to other AWS resources, we will need to update the Lambda’s IAM role and provide these privileges. This might be an RDS database, for example.

AWS has always used IAM to configure service to service access control. It is a well developed and well-documented model. It will probably be your primary mechanism for access control between microservices (within AWS). There might be cases where you need to augment or replace it, but I would start with IAM.

We can now build the web application for our users.

React Web Application

I am going to build a React single page web application (SPA). A Vue.js or Angular application would work too. For the client application, there are two significant components: AWS Amplify and the aws4 module.

AWS Amplify provides easy integration with AWS Cognito. aws4 is a popular library for signing AWS requests using AWS Request Signatures Version 4. AWS used signed requests for protected resources (i.e. authorized user requests).

Returning to the web client, we’ll use create-react-app. I won't outline the steps, as they are well documented on the create-react-app home page, and there are numerous online tutorials. (I've even written a few. )

For authentication, we need to do some state management. The example application doesn’t use any framework, but in a real application I’d suggest Mobx (or Redux.)

In the demo application, auth-store.js manages the user authentication state. This consists of the user's authentication state and credentials. These are used to

  • render different components and styles for authenticated vs. guest user
  • sign requests for protected API methods

While AWS Amplify manages much of the AWS Cognito integration, there is some work for us to do.

Determining Auth State from AWS Amplify

AWS Amplify’s documentation is good in some areas and deficient in others. I suggest reading the Authentication section of the Amplify documentation. This describes theAuth component, which interacts with Cognito.

However, there are still some aspects that the documentation doesn’t clearly address. AWS Amplify doesn’t make it easy to know the authentication state. (A discussion of this complexity is here.) Amplify configures itself asynchronously, without a callback. But there is an aws-amplify class that can help.

The Hub class in the aws-amplify module behaves like an event emitter. We care about two events: configured and cognitoHostedUI.

After the AWS Amplify configures the Auth component, it emits the configured event. Our application can then inquire about the current user's authentication status. This is useful when our application is being loaded, for example.

While using the application, we need to know if the authentication state changes. There is a sign-in event, but it isn't the event we want, as our demo application uses OAuth and the Cognito Hosted UI. The sign-in event is used in a custom sign-in/up screen or when using the built-in Amplify React UI. For OAuth, Amplify dispatches the cognitoHostedUI event after a completed OAuth sign-in flow.

Signing Requests

The current user will have credentials issued by AWS Cognito. These contain an access id, a secret key, and a session key. These are available by calling Auth.currentCredentials() in aws-amplify. For API methods authorized by IAM, you need to sign the request using AWS V4 Request Signatures. Thankfully, the aws4 module handles the complexities of generating these signatures.

In api-client.js,

 import aws4 from "aws4"; const apiHost = process.env.REACT_APP_API_HOST; const apiKey = process.env.REACT_APP_API_KEY; const region = process.env.REACT_APP_REGION; export async function authenticatedCall(authStore) { const opts = { method: "GET", service: "execute-api", region: region, path: "/latest/require-auth", host: apiHost, headers: { "x-api-key": apiKey }, url: `//${apiHost}/latest/require-auth` }; const credentials = await authStore.getCredentials(); const { accessKeyId, secretAccessKey, sessionToken } = credentials; const request = aws4.sign(opts, { accessKeyId, secretAccessKey, sessionToken }); delete request.headers.Host; const response = await fetch(opts.url, { headers: request.headers }); if (response.ok) { return await response.json(); } else return { message: response.statusText }; } export async function noAuthCall(authStore) { const response = await fetch(`//${apiHost}/latest/no-auth`, { headers: { "x-api-key": apiKey } }); return await response.json(); } view rawapi-client.js hosted with ❤ by GitHub

Demo

We can finally run npm start and run the app! When we first arrive at the application, we are a guest (unauthenticated user). You can also go to //auth-api-demo.firebaseapp.com/ to try it out.

We can access unprotected methods.

But if we try to access a protected resource, it will fail.

But if we sign in, we can access the protected resources.

Click Sign In and use demouser with password of demoPASS123.

We can now click the Req. Auth button to access a protected API method.

Whew! We had to configure multiple services and digest a lot of information. But we now have an application that is a model for authenticating microservices on AWS.

Now What?

This article’s approach is “all-in” on AWS. This was a deliberate choice, to show how the various AWS pieces fit together to solve a common need, namely auth. There are alternatives to methods in this article, and I outline a few here.

And for those who stayed with me to the end, I have some parting gifts.

  • In the demo repo, there is a script for automating the AWS setup. Its README has the details for running it.
  • resources-cheatsheet.md has the specific links for relevant AWS, Claudia.js, etc. documentation.

Thanks for reading!