CognitoIdentityID is empty in Go lambda function with Cognito authentication and API Gateway - go

I have an AWS lambda function written in Go behind an API Gateway. I'm using Cognito with App integration and OAuth Client credentials grant for authentication. I need to know which App client sent the request inside the lambda function, but all the fields related to Cognito (CognitoIdentityID, CognitoIndetityPoolID, AccountID, etc.) are empty in the context and the request. I can only see the Bearer Authorization in the header of the request. I'm printing the values as follows:
var forwardRequest = func(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
println(fmt.Sprintf("Request arrived: Stage: %s, Method: %s, Path: %s",
request.RequestContext.Stage, request.HTTPMethod, request.Path))
lc, _ := lambdacontext.FromContext(ctx)
println(fmt.Sprintf("lc: %+v", lc))
println(fmt.Sprintf("request: %+v", request))
...
}
func main() {
// Make the handler available for Remote Procedure Call by AWS Lambda
lambda.Start(forwardRequest)
}
I'm already using Lambda Proxy integration, as answered in similar questions. I attach a screenshot of the configuration of the API Gateway method. I can't enable "Invoke with caller credentials".
How can I know which client sent the request?.

I found the answer: Cognito's client ID is not in the Cognito* fields but in the Authorizer of the request:
func(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
var clientID string
if claimsMap, ok := request.RequestContext.Authorizer["claims"].(map[string]interface{}); ok {
clientID, ok = claimsMap["client_id"].(string)
...
}
...

Related

how to pass parameter of the destination to middleware in gin/golang

My problem in short is:
I send my auth token as a parameter to my destination api and it seems like middleware can not access that. How can I access the parameter since the middleware needs that to check the auth conditions?
I am trying to implement a simple authentication/authorization application.
I know that it is common to set auth token in coockies, however, in my use-case, I need it to be implemented differently.
The implementation is: login returns auth token in response body and anytime authentication token is required, it is sent as a parameter "authorization" to the application.
here is the code for my user routers :
func UserRoute(router *gin.Engine) {
user := router.Group("/user")
{
user.POST("/signup", controllers.SignUp)
user.POST("/login", controllers.Login)
user.GET("/validate", middleware.RequireAuth, controllers.Validate)
}
}
validate function in usercontrollers.go:
func Validate(c *gin.Context) {
user, _ := c.Get("user")
c.IndentedJSON(http.StatusOK, gin.H{
"message": user,
})
}
here is the request I send
http://localhost:6000/user/validate?authorization=[My-JWT-Token]
Now when I try to read my auth parameter and use it in my middleware it seems like it does not actually exist:
func RequireAuth(c *gin.Context) {
confs, _ := configs.LoadConfig()
tokenString := c.Param("authorization")
if tokenString == "" {
// this abort case always happens
c.AbortWithStatus(http.StatusUnauthorized)
}
}
1. ctx.Request.URL.Query().Get("authorization")
2. ctx.Query("authorization")

How to implement authorization using Keycloak

I created a REST API in Go that is necessary an authorization layer, for this layer I am trying use Keycloak. The API will be consumed by a third-party backend service, anyone knows the workflow to integrate Go client and keycloak or already implemented it? I figured out an adapter called Gocloak but in its documentation there is not any example for this purpose.
Authorization is typically application specific, so I can't help much there, but here's some information on authenticating JWTs from Keycloak. After JWTs are authenticated, you can use their claims to authorize the request.
Keycloak exposes what's known as a JSON Web Key Set (JWKS). This resource should be used to authenticate JWTs. I've wrote a package for this purpose. It's an extension of github.com/golang-jwt/jwt/v4.
The package is called github.com/MicahParks/keyfunc. I've pasted the code example for Keycloak below.
package main
import (
"log"
"time"
"github.com/golang-jwt/jwt/v4"
"github.com/MicahParks/keyfunc"
)
func main() {
// Get the JWKS URL.
//
// This is a local Keycloak JWKS endpoint for the master realm.
jwksURL := "http://localhost:8080/auth/realms/master/protocol/openid-connect/certs"
// Create the keyfunc options. Use an error handler that logs. Refresh the JWKS when a JWT signed by an unknown KID
// is found or at the specified interval. Rate limit these refreshes. Timeout the initial JWKS refresh request after
// 10 seconds. This timeout is also used to create the initial context.Context for keyfunc.Get.
options := keyfunc.Options{
RefreshErrorHandler: func(err error) {
log.Printf("There was an error with the jwt.Keyfunc\nError: %s", err.Error())
},
RefreshInterval: time.Hour,
RefreshRateLimit: time.Minute * 5,
RefreshTimeout: time.Second * 10,
RefreshUnknownKID: true,
}
// Create the JWKS from the resource at the given URL.
jwks, err := keyfunc.Get(jwksURL, options)
if err != nil {
log.Fatalf("Failed to create JWKS from resource at the given URL.\nError: %s", err.Error())
}
// Get a JWT to parse.
jwtB64 := "eyJhbGciOiJQUzM4NCIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMeDFGbWF5UDJZQnR4YXFTMVNLSlJKR2lYUktudzJvdjVXbVlJTUctQkxFIn0.eyJleHAiOjE2MTU0MDY5ODIsImlhdCI6MTYxNTQwNjkyMiwianRpIjoiMGY2NGJjYTktYjU4OC00MWFhLWFkNDEtMmFmZDM2OGRmNTFkIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL21hc3RlciIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJhZDEyOGRmMS0xMTQwLTRlNGMtYjA5Ny1hY2RjZTcwNWJkOWIiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJ0b2tlbmRlbG1lIiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiY2xpZW50SG9zdCI6IjE3Mi4yMC4wLjEiLCJjbGllbnRJZCI6InRva2VuZGVsbWUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC10b2tlbmRlbG1lIiwiY2xpZW50QWRkcmVzcyI6IjE3Mi4yMC4wLjEifQ.Rxrq41AxbWKIQHWv-Tkb7rqwel3sKT_R_AGvn9mPIHqhw1m7nsQWcL9t2a_8MI2hCwgWtYdgTF1xxBNmb2IW3CZkML5nGfcRrFvNaBHd3UQEqbFKZgnIX29h5VoxekyiwFaGD-0RXL83jF7k39hytEzTatwoVjZ-frga0KFl-nLce3OwncRXVCGmxoFzUsyu9TQFS2Mm_p0AMX1y1MAX1JmLC3WFhH3BohhRqpzBtjSfs_f46nE1-HKjqZ1ERrAc2fmiVJjmG7sT702JRuuzrgUpHlMy2juBG4DkVcMlj4neJUmCD1vZyZBRggfaIxNkwUhHtmS2Cp9tOcwNu47tSg"
// Parse the JWT.
token, err := jwt.Parse(jwtB64, jwks.Keyfunc)
if err != nil {
log.Fatalf("Failed to parse the JWT.\nError: %s", err.Error())
}
// Check if the token is valid.
if !token.Valid {
log.Fatalf("The token is not valid.")
}
log.Println("The token is valid.")
// End the background refresh goroutine when it's no longer needed.
jwks.EndBackground()
}

AWS API Gateway WebSockets [POST]#connections Returning 404 NotFound

I connect a client (or a couple of clients) to the websockets endpoint in API Gateway.
Then, I try to post a message back to the client using these guidelines: https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-how-to-call-websocket-api-connections.html
// Send sends a message to a connection ID.
func Send(domain, stage, connectionID, message string) (events.APIGatewayProxyResponse, error) {
session := session.Must(session.NewSession())
endpoint := fmt.Sprintf("https://%s/%s/#connections/%s", domain, stage, connectionID)
apiClient := apigatewaymanagementapi.New(session, aws.NewConfig().WithEndpoint(endpoint))
connectionInput := apigatewaymanagementapi.PostToConnectionInput{
ConnectionId: aws.String(connectionID),
Data: []byte(message),
}
_, err := apiClient.PostToConnection(&connectionInput)
if err != nil {
fmt.Println(err.Error())
return events.APIGatewayProxyResponse{StatusCode: 500}, err
}
return events.APIGatewayProxyResponse{StatusCode: 200}, nil
}
It doesn't matter whether I invoke the Send function locally or a client sends a message and API Gateway invokes my publish Lambda where I loop through the connections and invoke Send for each of them.
The result is always the same.
NotFoundException:
status code: 404, request id: 7bb1546a-c2a7-4e98-92a0-fcc7ae175d7c
Things I've tried:
Escaped #connections and the actual connectionID
Made sure the client connection hasn't timed out
Made sure I have the correct AWS credentials in my environment variables
Made sure my Lambda has permissions to invoke API Gateway
Made sure the endpoint is in the correct format: https://{api-id}.execute-api.{region}.amazonaws.com/{stage}/#connections/{connection_id}
How can I successfully send messages to the clients?
Turns out this line
endpoint := fmt.Sprintf("https://%s/%s/#connections/%s", domain, stage, connectionID)
needs to turn into this
endpoint := fmt.Sprintf("https://%s/%s/", domain, stage)

GoLang AWS Lambda Function Missing Body from API Gateway Request

I'm using an AWS Lambda function to handle a request from an AWS API Gateway call. I'm sending a payload on the request, and I can verify in CloudWatch that the payload is being passed from the gateway to the lambda function. However, the body of the request is null inside my Lambda function.
I looked at this question: AWS Lambda Go function not getting request body when called via API GW
I am trying to replicate the answer there by using this library: https://github.com/aws/aws-lambda-go/blob/master/events/apigw.go, but I'm still not able to get the request body.
Here is my Lambda code:
package main
import (
"context"
"fmt"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
func handleRequest(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
fmt.Println("Body")
fmt.Println(request.Body)
fmt.Printf("Processing request data for request %s.\n", request.RequestContext.RequestID)
fmt.Printf("Processing request data for request %s.\n", request.RequestContext.RequestID)
fmt.Printf("Body size = %d.\n", len(request.Body))
fmt.Println("Headers:")
for key, value := range request.Headers {
fmt.Printf(" %s: %s\n", key, value)
}
return events.APIGatewayProxyResponse{Body: request.Body, StatusCode: 200}, nil
}
func main() {
lambda.Start(handleRequest)
}
I'm expecting to see some data after "Body" in Cloudwatch, but there is nothing.
The code in the original question is correct. The second argument to the handleRequest is of the type APIGatewayProxyRequest. In API Gateway, I was sending a normal request, not a proxy request. I redeployed my API Gateway route as a proxy request and got the request body I was expecting. I'm still not really sure whether my original request was failing to send the body, or if the structure of a normal request passed into the handleRequest function is different from that of a proxy request and so the APIGatewayProxyRequest type was unable to parse its body.

Get BasicAuth creds in Gin

I have made a switch to Gin to try it out. Before the move I accessed the BasicAuth credentials (app_id and token) using the request object like this:
appId, token, _ := r.BasicAuth()
The app_id needs to be found in my database on every call so I'm using Gin middleware for this:
func CheckAppId() gin.HandlerFunc {
return func(c *gin.Context) {
//how do I access the BasicAuth creds here?
}
}
but I'm not sure how to access the BasicAuth creds without the request object.
The gin context contains the http.Request object in the Request field.

Resources