英文:
How can I add GitHub as an identity provider for AWS Cognito with Terraform?
问题
我使用AWS Cognito构建React应用程序的身份验证层,并尝试尽快取得成功。我正在使用Terraform构建后端,并已成功将Google作为身份提供商。现在我想添加Github,但我找不到可以用于此目的的任何样本Terraform资源。我正在使用托管的UI测试配置,但一旦一切正常,就会直接将链接复制到我的React应用程序中。
我在GitHub中创建了一个OAuth应用程序,并使用了其中的凭据。
这是我的GitHub身份提供者资源(主要是在copilot的帮助下创建的)。
在托管的UI中,我可以看到GitHub身份验证按钮。
但是,如果我点击该链接,它会立即转到我的回调,显示错误,而不会跳转到GitHub。
“OIDC身份提供者的配置不受支持。请查看规范的文档。”
我找不到关于这个错误消息的任何描述,谷歌上也没有帮助解释问题是什么。有人可以帮忙吗?
英文:
I am using AWS Cognito to build out the authentication layer for my React app, and I'm trying to go for the quickest win possible. I'm using Terraform to build my backend, and have successfully got Google working as an identity provider. Now I want to add Github, but I'm unable to find any sample Terraform resources that I can use for this. I am using the hosted UI to test the configuration, but will copy the links directly into my react app once it's all working.
I have created an OAuth application in GitHub and used the credentials from that.
Here is my resource for the GitHub identity provider (which I came up with largely with the help from copilot):
resource "aws_cognito_identity_provider" "github_provider" {
user_pool_id = aws_cognito_user_pool.user_pool.id
provider_name = "GitHub"
provider_type = "OIDC"
provider_details = {
authorize_scopes = "openid"
client_id = "XXXXXXXXXXXXXXXXX"
client_secret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
oidc_issuer = "https://token.actions.githubusercontent.com"
attributes_request_method = "GET"
}
attribute_mapping = {
username = "sub"
}
}
In the hosted UI I can then see my GitHub authentication button:
However, if I click the link, it immediately goes to my callback with the error without going to GitHub:
http://localhost:3000/callback?error_description=Unsupported+configuration+for+OIDC+Identity+Provider.+Please+review+the+documentation+for+specification.&error=server_error
"Unsupported configuration for OIDC Identity Provider. Please review the documentation for specification."
I can't find any description of this error message on Google and it doesn't help explain what the problem is. Can anyone help?
答案1
得分: 1
我花了一天的时间进行试验和错误,但多亏了这篇文章,我才成功地使它工作:https://sst.dev/examples/how-to-add-github-login-to-your-cognito-user-pool.html
GitHub并未按照AWS的预期实现OCID,因此我不得不向我的API添加了两个代理,以便在AWS和GitHub之间正确格式化请求和响应。
以下是最终工作的`aws_cognito_identity_provider`:
```hcl
resource "aws_cognito_identity_provider" "github_provider" {
user_pool_id = aws_cognito_user_pool.user_pool.id
provider_name = "GitHub"
provider_type = "OIDC"
provider_details = {
authorize_scopes = "openid user:email"
client_id = "XXXXXXXXXXXXXXXXXX"
client_secret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
attributes_request_method = "GET"
oidc_issuer = "https://token.actions.githubusercontent.com"
authorize_url = "https://github.com/login/oauth/authorize"
token_url = "https://${var.api_domain}/auth/github-token-proxy"
attributes_url = "https://${var.api_domain}/auth/github-userinfo-proxy"
jwks_uri = "https://token.actions.githubusercontent.com/.well-known/jwks.json"
}
attribute_mapping = {
email = "email"
username = "sub"
}
}
对于token_url
代理,主要更改是附加了accept: "application/json"
,而Cognito似乎无法自行执行此操作:
import { corsMiddleware } from "../middleware/corsMiddleware";
import fetch from "node-fetch";
import parser from "lambda-multipart-parser";
import { Logger } from "@aws-lambda-powertools/logger";
export const handler = corsMiddleware(async (event) => {
const result = await parser.parse(event);
const logger = new Logger({ serviceName: "postAuthGithubTokenProxyHandler" });
logger.info(event);
const token = await (
await fetch(
`https://github.com/login/oauth/access_token?client_id=${result.client_id}&client_secret=${result.client_secret}&code=${result.code}`,
{
method: "POST",
headers: {
accept: "application/json",
},
}
)
).json();
logger.info(JSON.stringify(token));
return {
statusCode: 200,
body: JSON.stringify(token),
};
});
对于第二个代理,我不得不将授权标头从"Bearer"更改为"token"。我还不得不添加对/user/emails的调用,因为默认的/user端点并不会单独返回私人电子邮件:
import { corsMiddleware } from "../middleware/corsMiddleware";
import fetch from "node-fetch";
import { Logger } from "@aws-lambda-powertools/logger";
export const handler = corsMiddleware(async (event) => {
const logger = new Logger({
serviceName: "getAuthGithubUserInfoProxyHandler",
});
logger.info(event);
const bearerToken = event.headers["Authorization"].split("Bearer ")[1];
if (!bearerToken) {
return {
statusCode: 401,
body: JSON.stringify({
error: "Missing bearer token",
}),
};
}
const token = (await (
await fetch("https://api.github.com/user", {
method: "GET",
headers: {
authorization: `token ${bearerToken}`,
accept: "application/json",
},
})
).json()) as { id: string; email: string };
logger.info(JSON.stringify(token));
let email = token.email;
if (email === null) {
const emails = (await (
await fetch("https://api.github.com/user/emails", {
method: "GET",
headers: {
authorization: `token ${bearerToken}`,
accept: "application/json",
},
})
).json()) as { email: string; primary: boolean; verified: boolean }[];
logger.info(JSON.stringify(emails));
const primaryEmailObj = emails.find((email) => email.primary);
if (primaryEmailObj) {
email = primaryEmailObj.email;
}
}
return {
statusCode: 200,
body: JSON.stringify({
email: email,
sub: token.id,
}),
};
});
<details>
<summary>英文:</summary>
I've spent a day of trial and error on this, but mostly thanks to this article I was able to get it working: https://sst.dev/examples/how-to-add-github-login-to-your-cognito-user-pool.html
GitHub doesn't implement OCID the way AWS expects it to, so I had to add two proxies to my API to properly format the request and response between AWS and GitHub.
Here is the final working `aws_cognito_identity_provider`:
resource "aws_cognito_identity_provider" "github_provider" {
user_pool_id = aws_cognito_user_pool.user_pool.id
provider_name = "GitHub"
provider_type = "OIDC"
provider_details = {
authorize_scopes = "openid user:email"
client_id = "XXXXXXXXXXXXXXXXXX"
client_secret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
attributes_request_method = "GET"
oidc_issuer = "https://token.actions.githubusercontent.com"
authorize_url = "https://github.com/login/oauth/authorize"
token_url = "https://${var.api_domain}/auth/github-token-proxy"
attributes_url = "https://${var.api_domain}/auth/github-userinfo-proxy"
jwks_uri = "https://token.actions.githubusercontent.com/.well-known/jwks.json"
}
attribute_mapping = {
email = "email"
username = "sub"
}
}
for the `token_url` proxy, the main change is attaching the `accept: "application/json"` which Cognito doesn't seem to do on it's own:
import { corsMiddleware } from "../middleware/corsMiddleware";
import fetch from "node-fetch";
import parser from "lambda-multipart-parser";
import { Logger } from "@aws-lambda-powertools/logger";
export const handler = corsMiddleware(async (event) => {
const result = await parser.parse(event);
const logger = new Logger({ serviceName: "postAuthGithubTokenProxyHandler" });
logger.info(event);
const token = await (
await fetch(
https://github.com/login/oauth/access_token?client_id=${result.client_id}&client_secret=${result.client_secret}&code=${result.code}
,
{
method: "POST",
headers: {
accept: "application/json",
},
}
)
).json();
logger.info(JSON.stringify(token));
return {
statusCode: 200,
body: JSON.stringify(token),
};
});
For the second proxy, I had to change the authorization header from "Bearer" to "token". I also had to add in a call to /user/emails as the default /user endpoint doesn't return private emails on its own:
import { corsMiddleware } from "../middleware/corsMiddleware";
import fetch from "node-fetch";
import { Logger } from "@aws-lambda-powertools/logger";
export const handler = corsMiddleware(async (event) => {
const logger = new Logger({
serviceName: "getAuthGithubUserInfoProxyHandler",
});
logger.info(event);
const bearerToken = event.headers["Authorization"].split("Bearer ")1;
if (!bearerToken) {
return {
statusCode: 401,
body: JSON.stringify({
error: "Missing bearer token",
}),
};
}
const token = (await (
await fetch("https://api.github.com/user", {
method: "GET",
headers: {
authorization: token ${bearerToken}
,
accept: "application/json",
},
})
).json()) as { id: string; email: string };
logger.info(JSON.stringify(token));
let email = token.email;
if (email === null) {
const emails = (await (
await fetch("https://api.github.com/user/emails", {
method: "GET",
headers: {
authorization: token ${bearerToken}
,
accept: "application/json",
},
})
).json()) as { email: string; primary: boolean; verified: boolean }[];
logger.info(JSON.stringify(emails));
const primaryEmailObj = emails.find((email) => email.primary);
if (primaryEmailObj) {
email = primaryEmailObj.email;
}
}
return {
statusCode: 200,
body: JSON.stringify({
email: email,
sub: token.id,
}),
};
});
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论