.net 8 C# 集成 AWS Cognito SMS/Email 注冊與登錄
本文主要分為三個部分:
1、描述 cognito 涉及的專業術語 以及 交互流程
2、.net 集成的代碼
3、感想
* 閱讀提示 :鼠標懸停在 章節標題 上可見 文章目錄
1. Cognito 概念
1.1 關鍵詞
進入 Amazon Cognito,會先看到 user pool 的列表

|
cognito
|
亞馬遜(Amazon)云 提供的一種用戶管理服務,簡化用戶注冊、登錄和授權、鑒權相關的服務操作。
可以在 cognito 上創建用戶池,管理用戶注冊、登錄和鑒權相關的問題;
可以創建 identity pool 對用戶進行授權相關的約束;
支持多種身份驗證方式,例如 用戶 / 密碼 登錄,社交帳號( Facebook,Google 等)登錄,企業身份供應商
|
|
user pool
|
用戶池,多租戶 / 不同的服務供應商,擁有自己的用戶群體;
用戶池支持用戶注冊、登錄、鑒權、賬號恢復等。
|
|
identity pool
|
資格授權池,可以提供一些 AWS 的資格認證到通過驗證的用戶上
|
|
app client
|
用戶池所關聯的應用服務端,必須要配置到相關的用戶池上才允許訪問該用戶池
|
選定一個用戶池,可以查看該用戶池的信息,左側欄見其 功能 / 配置

user pool 功能 / 配置 目錄列表
* 為了降低描述的復雜度,此處僅做最小配置展開
* 加粗為需要重點關注;此處不展開 IdentityPool 以及 userPool.Security 和 userPool.Branding
|
|
Seconcd |
Third |
Description |
Remark |
|
User Pool
|
Overview |
|
用戶池的基本信息 |
集成時需要找到這個用戶池的 ID、ARN 等信息 |
|
Applications |
app client |
配置可訪問該用戶池的應用端信息 |
在此處可以限制一些讀寫的權限 |
|
|
User Management |
Users |
用戶池里面的每個用戶 |
相當于用戶表,可以自定義用戶屬性 custom user attribute |
|
|
Groups |
用戶分組,主要用于指定某個組內的用戶可以享有某些權限 |
按需求來,可選配置 |
||
|
Authentication |
Authentication methods |
用戶驗證方式的配置 |
可以在此處配置密碼驗證 或 通過短信驗證 或 通過郵箱驗證 |
|
|
Sign-in |
用戶登錄時的相關配置 |
例如密碼復雜度,登錄方式 |
||
|
Sign-up |
用戶注冊時的相關配置 |
|
||
|
Social and external providers |
通過第三方登錄的配置,像是通過 Facebook, Google, Amazon or Apple..等其他供應商來授權 |
|
||
|
Extensions |
通過一些自定義的驗證行為,觸發 AWS lambda function。此處是觸發器的配置。 |
此處的觸發條件是 cognito 規定好的。也即僅可以配置:要不要觸發,觸發哪一個function |
||
|
Security |
AWS WAF |
|
|
|
|
Threat protection |
|
|
||
|
Log streaming |
|
|
||
|
Branding |
Domain |
|
|
|
|
Managed login |
|
|
||
|
Message templates |
|
|
||
|
Identity Pool
|
Overview |
|
|
|
|
Authentication Providers |
|
|
|
|
|
Roles |
|
|
|
|
|
IAM Policies |
|
|
|
|
|
Data Synchronization |
|
|
|
Cognito 主要是做 用戶管理 的事情,它支持通過 密碼、手機短信、郵箱地址、其他企業供應商(例如 facebook、apple account 等) 等方式授權 token,
本文主要描述 自定義校驗 custom authentication 的方式,此處要求在 user pool 中配置 3 個 AWS lambda function ,cognito 將會觸發它的執行。
Lambda function 也是 Amazon web service 其中之一,此處可以簡單地把其當作一個AWS的 api 方法。
這里涉及到 3 個觸發器:define auth challenge,create auth challenge,verify auth challenge
|
Custom authentication trigger type
|
Define Auth Challenge |
custom authentication 的第一步,這里會返回 custom challenge name 到 cognito;同時也可以基于用戶邏輯,直接在此處發布 token 。 |
|
Create Auth Challenge |
custom authentication 流程的第二步,實現自定義身份驗證的步驟。通常在這一步中寫入驗證的答案,驗證的問題可以是 captchas 或者其他安全問題 或 驗證碼 等。 |
|
|
Verify Auth Challenge |
custom authentication 的第三步,校驗用戶輸入的答案與在 create auth challenge 中預設的答案是否一致 |
下文將描述我們的代碼會如何與 cognito 交互,又是如何觸發對應的 lambda function。
1.2. 流程圖
登錄步驟 1:輸入賬號

- 前端第一次調用接口,是提交登錄的請求;
- 后端調用 AWSSDK.CognitoIdentifyProvider 中 cognitoClient.InitiateAuthAsync 方法,這個方法是 cognito 支持的自定義驗證方式,它會自動觸發到
define auth challenge,create auth challenge; - 此處發送短信的方式是后端去調用了一個 AWS Lambda Function
登錄步驟 2:輸入驗證碼

- 前端第二次調用的接口,是提交登錄的驗證碼
- 后端會把驗證碼發送給 user pool 去做校驗,通過了校驗才能成功授權返回 token
* AWS Lambda Triggers
|
場景 |
SDK Function |
Trigger the Lambda |
|
sign-in 登錄 |
InitiateAuthAsync |
Define Auth Challenge, Create Auth Challenge |
|
RespondToAuthChallengeAsync |
Verify Auth Challenge |
|
|
sign-up 注冊 |
SignUpAsync |
Custom message trigger |
|
ConfirmSignUpAsync |
|
Define auth challenge
- 這個 trigger 用于決定下一個驗證步驟,可以在這里配置走向密碼驗證或者自定義的驗證方式。
- 基于正確的 user session 上,它進一步定義驗證的流程
例如,用戶通過密碼驗證之后,將進入到手機短信驗證
// cognito 將會觸發到配置在 user pool 上的 lambda trigger,event 是它的入參
exports.handler = async (event) => {
if (event.request.session.length === 1 && event.request.session[0].challengeName === 'SRP_A') {
// 是否直接發布 token
event.response.issueTokens = false;
// 是否驗證失敗
event.response.failAuthentication = false;
// 下一個驗證方式的名字
event.response.challengeName = 'PASSWORD_VERIFIER';
} else if (event.request.session.length === 2 && event.request.session[1].challengeName === 'PASSWORD_VERIFIER' && event.request.session[1].challengeResult === true) {
event.response.issueTokens = false;
event.response.failAuthentication = false;
event.response.challengeName = 'CUSTOM_CHALLENGE';
} else if (event.request.session.length === 3 && event.request.session[2].challengeName === 'CUSTOM_CHALLENGE' && event.request.session[2].challengeResult === true) {
event.response.issueTokens = true;
event.response.failAuthentication = false;
} else {
event.response.issueTokens = false;
event.response.failAuthentication = true;
}
return event;
};
Create auth challenge
- 一旦
Define Auth Challenge觸發器指明使用自定義驗證,那么Create Auth Challenge就會被觸發去生成驗證的內容。 - 在這個步驟中所創建的校驗內容,是用戶必須要答復的。例如 發到 SMS、Email 上的驗證碼,或者 CAPTCHA 之類的驗證問題。
例如在 Create Auth Challenge 上設定一個將發送到 Email 的驗證碼(one-time password,簡稱 OTP),
此時 Amazon Cognito 其實會存儲這個 event 內容,關聯到用戶的 session 和 用于 Verify Auth Challenge 步驟作為校驗的答案(這里是驗證碼)
lambda function 實現示例:
exports.handler = async (event) => {
if (event.request.challengeName === 'CUSTOM_CHALLENGE') {
const otp = '654321'; // Generate or set your OTP here
event.response.publicChallengeParameters = { otp: 'Enter the OTP sent to your email' };
event.response.privateChallengeParameters = { otp: otp };
event.response.challengeMetadata = 'CUSTOM_CHALLENGE';
}
return event;
};
Verify auth challenge
- 該觸發器在用戶提交驗證碼之后被觸發
- 該觸發器接收到用戶輸入的 驗證碼 后會與
Create Auth Challenge中的驗證碼相互匹配。如果校驗正確,cognito 會處理返回的 event 并生成 token
lambda function 實現示例:
// TriggerSource is VerifyAuthChallengeResponse_Authentication
exports.handler = async (event) => {
const expectedOtp = event.request.privateChallengeParameters.otp;
const userResponse = event.request.challengeAnswer;
event.response.answerCorrect = userResponse === expectedOtp;
return event;
};
2. 集成到 .net 8 api
.NET 中需要使用 AWS SDK 提供的 API 來與 Cognito 進行交互,
引用包:AWSSDK.CognitoIdentityProvider ,下文代碼使用的版本是 3.7.1.85
<PackageReference Include="AWSSDK.CognitoIdentityProvider" Version="3.7.1.85" />
添加引用
using Amazon.CognitoIdentityProvider;
using Amazon.CognitoIdentityProvider.Model;
cognitoClient 初始化:
var AWSregion = "ap-xxxxx-x";
_cognitoClient = new AmazonCognitoIdentityProviderClient(Amazon.RegionEndpoint.GetBySystemName(AWSregion));
2.1.1 注冊 [signup]
public async Task<SignUpResponse> SignupAsync(SignUpDto user)
{
var userName = string.IsNullOrEmpty(user.Email) ? user.PhoneNumber : user.Email;
var request = new SignUpRequest
{
// 在 cognito user pool 中配置允許訪問該 user pool 的 client 后,可以在cognito 上查看到這個 clientId
ClientId = _clientId,
// cognito 會校驗代碼復雜度,需要在 user pool 中配置;但此示例中密碼是沒有作用的
Password = "carcar@2024",
// 此處可以填手機號或郵箱地址; cognito user pool 中顯示的 userName 是 user pool 用戶管理意義上的 uuid
Username = userName
};
// 自定義的 user attribute
var nameAttribute = new AttributeType
{
Name = "name",
Value = user.Name
};
request.UserAttributes.Add(nameAttribute);
// 自定義的 user attribute
var emailAttribute = new AttributeType
{
Name = "email",
Value = user.Email
};
request.UserAttributes.Add(emailAttribute);
return await _cognitoClient.SignUpAsync(request);
}
可配置密碼復雜度配置

可以配置對 user attribute 的必填要求

查看 user attribute

2.1.2 注冊驗證 [signup-confirm]
var confirmSignUpRequest = new ConfirmSignUpRequest
{
ClientId = "client id",
Username = "user phone / email",
ConfirmationCode = "verification code from sms or email"
};
var confirmSignUpResponse = await cognitoClient.ConfirmSignUpAsync(confirmSignUpRequest);
Console.WriteLine($"User {confirmSignUpResponse.UserConfirmed} confirmed successfully.")
2.1.3 重新發送驗證碼 [resend-confirmation]
// userName 可以是 cognito UserName,又或者是注冊時寫入的 userName 即郵箱或手機號
public async Task ResendConfirmationAsync(string userName)
{
var request = new ResendConfirmationCodeRequest
{
ClientId = _clientId,
Username = userName
};
var response = await _cognitoClient.ResendConfirmationCodeAsync(request);
}
2.2.1 登錄 [signin]
var authRequest = new InitiateAuthRequest
{
ClientId = "the client id which configures in the user pool",
AuthFlow = AuthFlowType.CUSTOM_AUTH,
AuthParameters = new Dictionary<string, string>
{
{ "USERNAME", "your_username_or_phone_or_email" }
}
};
var authResponse = await cognitoClient.InitiateAuthAsync(authRequest);
2.2.2 登錄驗證 [signin-confirm]
校驗驗證碼時,調用 RespondToAuthChallengeAsync
var respondToAuthChallengeRequest = new RespondToAuthChallengeRequest
{
ChallengeName = authResponse.ChallengeName,
ClientId = "your_client_id",
ChallengeResponses = new Dictionary<string, string>
{
{ "USERNAME", "your_username_or_phone_or_email" },
{ "SMS_MFA_CODE", "user_received_code" }
},
Session = authResponse.Session
};
var challengeResponse = await cognitoClient.RespondToAuthChallengeAsync(respondToAuthChallengeRequest);
var idToken = challengeResponse.AuthenticationResult.IdToken;
2.3 token 驗證
在 Program 或 StartUp 的配置文件中,需要配置 Authentication 中間件
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = $"https://cognito-idp.{AWSregion}.amazonaws.com/{userPoolId}",
// ValidAudience = {userPoolId},
IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) =>
{
// Get JsonWebKeySet from AWS
var json = new WebClient().DownloadString($"https://cognito-idp.{AWSregion}.amazonaws.com/{userPoolId}/.well-known/jwks.json");
// Deserialize the result
var keys = JsonConvert.DeserializeObject<JsonWebKeySet>(json).Keys;
// Cast the result to be the type expected by IssuerSigningKeyResolver
return (IEnumerable<SecurityKey>)keys;
}
};
});
3. 為什么寫這一篇文章
很多開發可能和我一樣,沒有成為一名云上工程師,或者說項目里并沒有使用 Amazon Web Service,那么對于如何集成 AWS 可能是不感興趣的。
那我為什么寫這篇文章?
1、國內對 AWS 的應用較少,AWS 相關的資料大多是英文的,其實解讀下來真的挺花時間。
2、現在新穎的技術層出不窮,我的希望是在探索 cognito 的過程中,建立一個快速理解的方法論。
這背后考驗的是專業知識以及邏輯梳理能力。我們一定是:知其所以然,才能應對多變的表象。
當然,這里我指的是對云服務如何集成傳統應用,或者說“我的應用要怎么上云”。
3、借此機會看一下云服務的設計,現在都怎么玩的。
理解云服務的應用不單止為我們多提供一種解決方案,在排查集成云集成中產生的問題,也會有所啟發。
是否能夠以小見大,找到表象的本質?
在既定的解決方案架構里面找到可以拓展的共性,能不能經驗遷移?
無論如何,希望這篇文章對你有所收益。
References
[1] Amazon Cognito Identity Provider examples using AWS SDK for .NET
[2] Authentication flow examples with .NET for Amazon Cognito
[3] Authenticating users in ASP.NET Core MVC using Amazon Cognito
[4] Securing ASP.NET Core API with JWT Token using AWS Cognito

浙公網安備 33010602011771號