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")
Related
TLDR: I am looking for a way to update headers on an open stream for each call to stream.Send(msg) without closing the stream and opening a new one.
Summary
I have a GRPC client and server built to handle bidirectional streams. To authenticate with the server the client must send a JWT in the request headers, set as "authorization". The token is valid for 30 minutes. After the token has expired, the server will terminate the connection.
I am looking for a way to refresh my authorization token from the client, and keep the stream open. The client should run in a loop executing a new request every 30 minutes with the updated token, and the updated payload. I have not seen a way to update a header from the client side for an already opened stream.
Let's look at some code to get an idea of what the client side looks like. The code below has a function to create a new instance of the client, and another function to establish the connection to the GRPC server.
func NewWatchClient(config *Config, logger *logrus.Logger) (*WatchClient, error) {
cc, err := newConnection(config, logger)
if err != nil {
return nil, err
}
service := proto.NewWatchServiceClient(cc)
return &WatchClient{
config: config,
conn: cc,
logger: entry,
service: service,
}, nil
}
func newConnection(config *Config, logger *logrus.Logger) (*grpc.ClientConn, error) {
address := fmt.Sprintf("%s:%d", config.Host, config.Port)
// rpcCredential implements credentials.PerRPCCredentials
rpcCredential := newTokenAuth(config.Auth, config.TenantID)
return grpc.Dial(
address,
grpc.WithPerRPCCredentials(rpcCredential),
)
}
Looking at the newConnection function above I can see that there is a call to another function, newTokenAuth, to create an auth token. This func returns a struct that implements the PerRPCCredentials interface.
There are two ways to set the authorization for a request.
Use grpc.WithPerRPCCredentials to add the authorization at the time of creating the connection to the server.
Use grpc.PerRPCCredentials to add the authorization to each stream opened on the connection to the server.
In this case, I am using grpc.WithPerRPCCredentials to attach the token at the time of creating the connection to the server.
Now, let's take a look at the definition of PerRPCCredentials.
type PerRPCCredentials interface {
// GetRequestMetadata gets the current request metadata, refreshing
// tokens if required. This should be called by the transport layer on
// each request, and the data should be populated in headers or other
// context. If a status code is returned, it will be used as the status
// for the RPC. uri is the URI of the entry point for the request.
// When supported by the underlying implementation, ctx can be used for
// timeout and cancellation. Additionally, RequestInfo data will be
// available via ctx to this call.
// TODO(zhaoq): Define the set of the qualified keys instead of leaving
// it as an arbitrary string.
GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
// RequireTransportSecurity indicates whether the credentials requires
// transport security.
RequireTransportSecurity() bool
}
The interface requires that you define two methods. The documentation of GetRequestMetadata says
GetRequestMetadata gets the current request metadata, refreshing tokens if required
So, it looks like my implementation of PerRPCCredentials should be able to handle a token refresh for my stream or connection. Let's take a look at my implementation of PerRPCCredentials.
// tokenAuth implements the PerRPCCredentials interface
type tokenAuth struct {
tenantID string
tokenRequester auth.PlatformTokenGetter
token string
}
// RequireTransportSecurity leave as false for now
func (tokenAuth) RequireTransportSecurity() bool {
return false
}
// GetRequestMetadata sets the http header prior to transport
func (t tokenAuth) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) {
token, err := t.tokenRequester.GetToken()
if err != nil {
return nil, err
}
t.token = token
go func() {
time.Sleep(25 * time.Minute)
token, _ := t.tokenRequester.GetToken()
t.token = token
}()
return map[string]string{
"tenant-id": t.tenantID,
"authorization": "Bearer " + t.token,
}, nil
}
As you can see, the call to GetRequestMetadata will establish a go routine that will attempt to refresh a token every 25 minutes. Adding a go routine right here is probably not the right way to do it. It was an attempt to get the auth header to refresh, which doesn't work.
Let's take a look at the stream.
func (w WatchClient) CreateWatch() error {
topic := &proto.Request{SelfLink: w.config.TopicSelfLink}
stream, err := w.service.CreateWatch(context.Background())
if err != nil {
return err
}
for {
err = stream.Send(topic)
if err != nil {
return err
}
time.Sleep(25 * time.Minute)
}
}
The client sends a message on the stream every 25 minutes. All I'm looking to get here is that when stream.Send is called, the updated token is also sent.
This function, GetRequestMetadata only gets called once, regardless if I am setting the auth through grpc.WithPerRPCCredentials or grpc.PerRPCCredsCallOption so there appears to be no way to update the authorization header.
If you have any idea what I have missed in my attempt to utilize the PerRPCCredentials for token refresh then please let me know.
Thank you.
Headers are sent at the beginning of an RPC, and cannot be updated during the RPC. If you need to send data during the life of a stream, it needs to be part of the request message in your proto definition.
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()
}
I'm using LabStack's Golang Echo Framework to build out a service.
One of the routes, needs to proxy requests and responses to and from a backend service.
But I also need CORS to work on this service as well.
So I'm using middleware.CORSWithConfig along w/ a middleware.ProxyWithConfig in my request/response stack.
I'm seeing some oddness w/ the Access-Control-Allow-Origins header where the value for that header on the response from the proxied service to my Echo server *, but once it passes through the proxy, it changes to *, * by the time it gets back into the client.
Upon which I start seeing the following browser errors related to CORS violations:
VM1627:362 Access to XMLHttpRequest at 'http://localhost:6273/' from origin 'http://localhost:8002' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed.
Has anyone come across this? Anyone have any idea why this might be happening and maybe a way around it?
Here's some example code:
package main
func singleTargetBalancer(url *url.URL) middleware.ProxyBalancer {
targetURL := []*middleware.ProxyTarget{
{
URL: url,
},
}
return middleware.NewRoundRobinBalancer(targetURL)
}
func Noop(ctx echo.Context) (err error) {
ctx.String(
http.StatusNotImplemented,
"No op handler should never be reached!",
)
return err
}
func main() {
e := echo.New()
e.HideBanner = true
e.Use(
middleware.CORSWithConfig(middlewares.CustomCorsConfig),
middlewares.ThriftMetrics(),
)
// Have to use a Noop handler since we're not trying to set up a full-on proxy for the backend service. We only want this one route to be proxied.
e.POST(
"/",
handlers.Noop,
middleware.ProxyWithConfig(middleware.ProxyConfig{
Balancer: singleTargetBalancer("[backend service URL]"),
})
)
}
I ultimately solved this by writing a custom Echo middleware to hook into the response before Echo's proxy middleware could send the headers back to the client.
func setResponseACAOHeaderFromRequest (req http.Request, resp echo.Response) {
resp.Header().Set(echo.HeaderAccessControlAllowOrigin,
req.Header.Get(echo.HeaderOrigin))
}
func ACAOHeaderOverwriteMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(ctx echo.Context) error {
ctx.Response().Before(func() {
setResponseACAOHeaderFromRequest(*ctx.Request(), *ctx.Response())
})
return next(ctx)
}
}
Then just put this middleware in e.Use() right before your proxy middleware:
e.POST(
"/",
handlers.Noop,
ACAOHeaderOverwriteMiddleware,
middleware.ProxyWithConfig(middleware.ProxyConfig{
Balancer: singleTargetBalancer("[backend service URL]"),
})
)
Docs for Echo's Request::Before() hook: https://echo.labstack.com/guide/response#before-response
I have a website project. It uses Go and the Gorilla and it's CSRF packages to protect against CSRF. I also have a JSON API that authenticates using a JWT like token provider (internal), so a user must authenticate with that before issuing a JSON request each time. So the CSRF is not an issue on the JSON side. At least I don't think so.
Here's my code, where I am using a NewRouter for web Paths, and a Subrouter for the /api/v1/[endpoint]s. If I call a JSON endpoint that does a POST, the CSRF is engaged and I get a Forbidden - CSRF token invalid. I was under the assume, that perhaps a Sub Router would not have the middleware for the CSRF check associated with.
router := mux.NewRouter().StrictSlash(false)
router.Path("/").HandlerFunc(myApp.IndexHandler).Methods("GET")
apiRouter := router.PathPrefix("/api").Subrouter()
apiRouter.Path("/dosomething").HandlerFunc(myApp.DoSomethingAPIHandler).Methods("POST", "OPTIONS")
http.ListenAndServe(":8000",
csrf.Protect(
[]byte("my-long-key-here-redacted"),
csrf.Secure(false), // Set to false as we offload SSL elsewhere
)(router)))
Question:
How do I get my API to work with or without CSRF protection? Obviously, the web paths will need to be protected to protect form posts.
One option is to only use the CSRF protection on specific HTTP handlers, rather than protecting the entire router. Note that this will require you to perform a type conversion on your myApp.IndexHandler in order to satisfy the type signature for the function returned by csrf.Protect().
router := mux.NewRouter().StrictSlash(false)
// Instead of protecting your entire router, you can protect specific HTTP
// handlers.
router.Path("/").Handler(
csrf.Protect(
[]byte("my-long-key-here-redacted"),
csrf.Secure(false),
)(http.HandlerFunc(myApp.IndexHandler)),
).Methods("GET")
apiRouter := router.PathPrefix("/api").Subrouter()
apiRouter.Path("/dosomething").HandlerFunc(myApp.DoSomethingAPIHandler).Methods("POST", "OPTIONS")
http.ListenAndServe(
":8000",
router,
)
Alternatively, you can use the function returned from csrf.Protect() to create your own middleware, with logic to only add the CSRF protection on certain requests. You could use this approach to only add protection on endpoints with the prefix /api for example, as I've done in the code below.
protectionMiddleware := func(handler http.Handler) http.Handler {
protectionFn := csrf.Protect(
[]byte("my-long-key-here-redacted"),
csrf.Secure(false),
)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Use some kind of condition here to see if the router should use
// the CSRF protection. For the sake of this example, we'll check
// the path prefix.
if !strings.HasPrefix(r.URL.Path, "/api") {
protectionFn(handler).ServeHTTP(w, r)
return
}
handler.ServeHTTP(w, r)
})
}
router := mux.NewRouter().StrictSlash(false)
router.Path("/").HandlerFunc(myApp.IndexHandler).Methods("GET")
apiRouter := router.PathPrefix("/api").Subrouter()
apiRouter.Path("/dosomething").HandlerFunc(myApp.DoSomethingAPIHandler).Methods("POST", "OPTIONS")
http.ListenAndServe(
":8000",
protectionMiddleware(router),
)
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.