Xem các bài viết trước cùng series:

Serverless Series (Golang) - Bài 1 - Serverless và AWS Lambda

Serverless Series (Golang) - Bài 2 - Build REST API with AWS API Gateway

Serverless Series (Golang) - Bài 3 - Integrate AWS Lambda with DynamoDB for data persistence

Serverless Series (Golang) - Bài 4 - Integrate AWS Lambda with S3 for file storage and hosting web app

Serverless Series (Golang) - Bài 5 - Authentication with Amazon Cognito - Part 1

Hướng dẫn học AWS cho người mới bắt đầu - Learn AWS the Hard Way

Tác giả Huỳnh Minh Quân là giảng viên khóa học AWS thực hành và luyện thi chứng chỉ : Learn AWS the Hard Way

Giới thiệu

Chào các bạn tới với series về Serverless, tiếp tục ở phần trước sau khi ta đã implement được hai hàm là change password và login, thì ở phần này ta sẽ xây dựng tiếp phần authentication cho những API mà ta muốn user phải cần đăng nhập thì mới gọi tới được.

Hệ thống mà ta đang xây dựng như sau.

Phần tiếp theo ta sẽ làm là phần check token.

Thông thường, đối với những trang SPA, khi ta làm một chức năng liên quan tới phần authentication, thì sau khi làm xong phần trả về được token cho user khi user đăng nhập, ta phải làm tiếp chức năng middleware để xác thực lại token đó khi mà user gọi tới những API mà yêu cầu phải đăng nhập. Tùy vào yêu cầu bảo mật của chúng ta thì việc implement middleware sẽ là rất dễ hoặc rất khó. Thì để tránh mất thời gian, ta có thể sử dụng chức năng xác thực token có sẵn của API Gateway khi kết hợp nó với Cognito.

Securing API Gateway with Cognito

Hệ thống của ta sẽ có hai API mà ta muốn user cần đăng nhập thì mới gọi được là POST /books và DELETE /books, vì ta không muốn ai truy cập vào trang của ta cũng có thể tạo dữ liệu và xóa dữ liệu được cả, chỉ user nào đăng nhập thì mới có thể tạo dữ liệu.

Ta sẽ làm các bước sau để bảo mật cho POST /books và DELETE /books.

  1. Truy cập API Gateway console https://console.aws.amazon.com/apigateway.
  2. Bấm vào books-api
  3. Ở mục API: books-api chọn Authorizers.

image.png

  1. Bấm vào nút Create New Authorizer, ta sẽ thấy UI như sau.

image.png

Điền tên theo ý của bạn, chỗ Type các ta chọn Cognito, chỗ Cognito User Pool chọn cognito-serverless-series mà ta đã tạo ở bài trước. Chỗ Token Source, điền vào theo ý bạn, nếu ta điền là Authorization thì khi ta gọi request tới API Gateway, ta cần truyền token vào header với key là Authorization.

  1. Bấm tạo

Oke, giờ ta đã integrate được Cognito vào API Gateway để nó có thể secure một endpoints bất kì nào mà ta muốn. Để chỉ định API nào mà sẽ thực hiện việc kiểm tra token của một request, ta làm như sau.

  1. Di chuyển tới API: books-api chọn Resources.
  2. Ở mục Resources bấm vào POST method.

image.png

  1. Bấm vào Method Request
  2. Ở mục Authorization, chọn Cognito mà ta vừa mới tạo.

image.png

  1. Các mục còn lại để mặc định và bấm Deploy lại API.

image.png

Sau khi deploy xong, giờ khi bạn gọi lại API POST /books, nó sẽ trả về lỗi là Unauthorized.

image.png

Oke, vậy là đã đúng được mục đích mà ta muốn.

Để gọi được API mà có set Authorization, ta sẽ truyền token được trả về từ API login vào headers khi gọi API. Kết quả trả về của hàm login.

{
    "AuthenticationResult": {
        "AccessToken": "eyJ...",
        "ExpiresIn": 3600,
        "IdToken": "eyJ...",
        "NewDeviceMetadata": null,
        "RefreshToken": "eyJ...",
        "TokenType": "Bearer"
    },
    "ChallengeName": "",
    "ChallengeParameters": {},
    "Session": null,
    "ResultMetadata": {}
}

Giá trị mà ta sẽ sử dụng là trường IdToken. Khi gọi tới API POST /books, ta truyền thêm vào headers với key là { "Authorization": Bearer <IdToken> }, sau đó API Gateway sẽ tự động gọi tới Cognito để thực hiện việc kiểm tra token tự động cho ta, ta không cần phải tự viết hàm middleware 😁.

Lambda Environment

Ta sẽ cập nhật lại function của ta một chút để nó dễ sử dụng hơn. Ở đoạn code kết nối tới Cognito của hàm login và change-password thì ta đang fix cứng ClientId, như vậy khi ta cần thay đổi ClientId thì ta phải build lại code và update nó lên lại Lambda, việc đó khá mất công. Nên ta sẽ sử dụng Lambda Environment để truyền các giá trị cấu hình vào hàm của ta.

Truy cập Lambda Console, bấm vào change_password function. Bấm qua tab Configuration, chọn Edit.

image.png

Chọn Add environment variable, điền vào Key là COGNITO_CLIENT_ID và Value là giá trị của client id của Cognito. Bấm Save.

image.png

Sau đó ta cập nhật lại change-password function như sau.

...
authInput := &cognitoidentityprovider.InitiateAuthInput{
    AuthFlow: "USER_PASSWORD_AUTH",
    ClientId: aws.String(os.Getenv("COGNITO_CLIENT_ID")),
    AuthParameters: map[string]string{
        "USERNAME": body.Username,
        "PASSWORD": body.OldPassword,
    },
}
...

challengeInput := &cognitoidentityprovider.RespondToAuthChallengeInput{
    ChallengeName: "NEW_PASSWORD_REQUIRED",
    ClientId:      aws.String(os.Getenv("COGNITO_CLIENT_ID")),
    ChallengeResponses: map[string]string{
        "USERNAME":     body.Username,
        "NEW_PASSWORD": body.NewPassword,
    },
    Session: authResp.Session,
}
...

Build code lại và update lại AWS Lambda function.

sh build.sh
aws lambda update-function-code --function-name change_password --zip-file fileb://change-password.zip --region us-west-2

Các bạn làm tương tự cho hàm login nhé. Sau đó các bạn nhớ kiểm tra lại xem function có hoạt động đúng như cũ không nha.

Kết luận

Vậy là ta đã tìm hiểu cách kết hợp API Gateway và Cognito để secutiry cho một API, như các bạn thấy thay vì ta phải làm tùm lum thứ cho chức năng authentication như đăng nhập, đăng ký, quên mật khẩu, xác nhận email, xác thực token, chọn phương thức để hash password, v…v… thì Cognito có sẵn những chức năng này cho ta, và rất dễ dàng xài. Nếu có thắc mắc hoặc cần giải thích rõ thêm chỗ nào thì các bạn có thể hỏi dưới phần comment. Hẹn gặp mọi người ở bài tiếp theo.