+13

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

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.

Site Reliability Engineering

Đây là quyển sách mình viết với những gì học được ở Vikki Digital Bank: "On-Call In Action."

Trong quá trình xây dựng hệ thống thì ngoài việc thiết kế hệ thống có thể mở rộng, đáp ứng được lượng truy cập cao, luôn luôn sẵn sàng để đáp ứng người dùng. Thì vấn đề cũng quang trọng không kém là phát hiện được hệ thống đang gặp vấn đề gì. Để làm được này thì ta phải định nghĩa được hệ thống thế nào là ổn định bằng các chỉ số, sau đó nếu hệ thống vượt quá chỉ số này thì ta cần phải bắn cảnh cáo, đó là công việc của Site Reliability Engineering và DevOps. Đối với hệ thống ngân hàng thì việc này lại càng quan trọng, khách hàng luôn cần chuyển tiền và hệ thống phải luôn sẵn sàng để đáp ứng.

Việc định nghĩa hệ thống không ổn định không phải chỉ dựa vào các chỉ số như CPU và Memory, đây là cái sai mà mình thấy nhiều bạn mắc phải nhất. Trong quyển sách này mình có hướng dẫn làm thế nào để xây dựng một hệ thống luôn sẵn sàng cho người dùng, cuốn sách không nói về thiết kế hệ thống mà hướng dẫn cách phát hiện sự bất ổn trong hệ thống và sửa, đây là vấn đề thực tế hơn.

Banking Infrastructure on Cloud

Loạt bài viết ngắn chia sẻ về kiến trúc hạ tầng của hệ thống ngân hàng trên Cloud (AWS): Vikki Digital Bank


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí