I'm beginner with Go, ruby my previous language. I started with Mux for build an API. Now I'm learning authentication using JWT to protect the API. Firstly I try to handle authentication in a function with single file main.go
func ListPosts(w http.ResponseWriter, r *http.Request){
// verify authentication here
// other stuff to get list of posts by a user authentication here
}
func handleRequests() {
myRouter := mux.NewRouter().StrictSlash(true)
myRouter.HandleFunc("/posts", ListPosts).Methods("GET")
log.Fatal(http.ListenAndServe(":8081", myRouter))
}
func main() {
handleRequests()
}
Everything is working well, the above implementation is repeating the authentication every I create a new endpoint, so I'm trying to make it DRY
Here is my main.go now :
func handleRequests() {
myRouter := mux.NewRouter().StrictSlash(true)
myRouter.HandleFunc("/posts", jwt.Authenticate(controllers.ListPosts)).Methods("GET")
log.Fatal(http.ListenAndServe(":8081", myRouter))
}
func main() {
handleRequests()
}
this is the jwt package I have made :
package jwt
func Authenticate(handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// checking header Authorization here
token, err := ValidateToken(r)
if err != nil {
// checking signature invalid here
// checking expire token here
// response error here
}
if !token.Valid {
// response error here
}
getClaim := token.Claims.(*Token)
// I'm using https://github.com/Kamva/mgm (mongo)
user := &models.User{}
coll := mgm.Coll(user)
// Find and decode the doc to a user model here
handler.ServeHTTP(w, r)
return
}
}
The authentication is working well, and now I want to get a user authentication in controllers package, do I need to put an UID of user on header and find a user with it? I believe that make a redudant, because I have find a user on Authenticate function.
package controllers
func ListPosts(w http.ResponseWriter, r *http.Request) {
// should I find a user with r.Header["UID"] here ?
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode({})
}
Related
I'm trying to find a way to add a correlation/request id for logs in our project to make it easier to navigate through them and debug when some issues occur. I found this article. From the example there, there is a middleware to add the correlationID and then retrieve it in some handler function.
Middleware function:
const ContextKeyRequestID ContextKey = "requestID"
func reqIDMiddleware1(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
id := uuid.New()
ctx = context.WithValue(ctx, ContextKeyRequestID, id.String())
r = r.WithContext(ctx)
log.Debugf("Incoming request %s %s %s %s", r.Method, r.RequestURI, r.RemoteAddr, id.String())
next.ServeHTTP(w, r)
log.Debugf("Finished handling http req. %s", id.String())
})
}
Handler:
const LogFieldKeyRequestID = "requestID"
func handleSomeRequest() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
reqIDRaw := ctx.Value(ContextKeyRequestID) // reqIDRaw at this point is of type 'interface{}'
reqID, ok := reqIDRaw.(string)
if !ok {
// handler error
}
// if reached here, reqID is ready to be used
// let's use it with logrus FieldLogger!
logger := log.WithField(LogFieldKeyRequestID, reqID)
// Do something, then log what you did
logger.Debugf("What I just did!")
// Do more, log more. Handle this request seriously
}
}
But I was wondering if there is a way to achieve this without having to refactor all the existing handlers and changing the logging functionality, through some automatic configuration that would add id for each log, in my case our project is quite big, and doing it in the way described above would require a lot of changes.
Have you looked at WithContext and Hooks?
You still have to modify your code but you can centralize some behaviours.
https://go.dev/play/p/4YxJMK6Zl5D
Using this https://github.com/praveen001/go-passport
Path with passsport middleware:
app.Put("/api/auth/login", p.Authenticate("local", MyHandler))
The passport authenticate function:
// Authenticate calls `Strategy.Authenticate` method of registered strategies, and checks the `passport.Result` returned by it.
//
// The result is stored in the request context with `passport.CtxKey` as key.
//
func (p *Passport) Authenticate(name string, h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
s, ok := p.Options.strategies[name]
if !ok {
w.WriteHeader(404)
return
}
s.Authenticate(w, r, func(res *Result) {
res.StrategyName = name
ctx := context.WithValue(r.Context(), CtxKey, res)
h.ServeHTTP(w, r.WithContext(ctx))
})
}
}
So this attach the response from my auth method and returns a http.HandleFunc which I can define myself (MyHandler).
func MyHandler(w http.ResponseWriter, r *http.Request) {
// Where is the data attached from the Authenticate func?
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(401)
io.WriteString(w, `{"status":"ok"}`)
}
I dont understand where I can reach the attached data from the Authenticate func. The comment says: "//The result is stored in the request context with passport.CtxKey as key".
I don't find it in the r *http.Request.
The http.Request object has a Context() method which gives you the context. You access the key through that.
E.g.
ctx := r.Context()
value := ctx.Value(passport.CtxKey)
im using a middleware (CheckToken) to check a JWT and get the custom claim (Id) (it will be the id of the user on my DB) but i need to pass it to campaign.Attack (so i can know who is the user who is doing the "attack") but i cant find out a way to do it.
i tried to pass it as a parameter in next(w, req, claim.id) in token.go but i would need to touch the http.HandlerFunc function so this isnt a valid option.
any idea about how to pass the claim.id from CheckToken to campaign.Attack() ?
thank you
***** main.go*****
func main() {
router := mux.NewRouter()
router.HandleFunc("/attack", token.CheckToken(campaign.Attack)).Methods("GET", "OPTIONS")
log.Fatal(http.ListenAndServe(":3000", handlers.CORS(handlers.AllowedOrigins([]string{"*"}),
handlers.AllowedHeaders([]string{"Content-Type", "authorization"}))(router)))
}
******campaign.go*****
package campaign
import (
"log"
"net/http"
)
func init() {
}
func Attack(w http.ResponseWriter, req *http.Request) {
log.Println("attack")
//i need to get the claim.Id here
}
****token.go****
type MyCustomClaims struct {
Id int `json:"id"` //the Id of the user
jwt.StandardClaims
}
func CheckToken(next http.HandlerFunc) (MyCustomClaims, http.HandlerFunc) {
return MyCustomClaims{}, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
authorizationHeader := req.Header.Get("authorization")
if authorizationHeader != "" {
bearerToken := strings.Split(authorizationHeader, " ")
if len(bearerToken) == 2 {
token, err := jwt.ParseWithClaims(bearerToken[1], &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte("magicword"), nil
})
if token.Valid {
if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid {
//**************************
//***********i have the claims.id here and it works.*******
//**************************
log.Println(claims.Id)
//but i need to pass it or find a way to read it in campaign.Attack()
next(w, req)
} else {
log.Println(err)
}
} else if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
} else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
// Token is either expired or not active yet
}
}
}
}
})
}
Use context's method WithValue.
Instead next(w, req) write
ctx := context.WithValue(r.Context(), "claim_id", claims.Id)
next(w, req.WithContext(ctx))
and inside attack:
claim_id, ok := r.Context().Value("claim_id").(int)
if !ok {
return // I don't have context .. sorry
}
// use claim_id
What I haven't mention, how to create an unique key ... but sometimes in the future.
Look into CheckToken .. It should be middleware, and typical midleware pass http.Handler and maybe another argument and return another http.Handler (this is bit better than using http.HandlerFunc). Returned function typically call argument and do some action before or after this call.
func CheckToken(next http.Handler) http.Handler
{
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// and body the same as you have in the answer, but instead of next(w, req) put there two lines "WithValue"
}
}
and into begin of Attack put next few lines that I've already give you
I'm having difficulty authenticating using Google OAuth2.
I've obtainedd client ID and secret from google developer console, and I came up with this code :
package main
import (
"fmt"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"io/ioutil"
"net/http"
"os"
)
const htmlIndex = `<html><body>
Log in with Google
</body></html>
`
func init() {
// Setup Google's example test keys
os.Setenv("CLIENT_ID", "somrestring-otherstring.apps.googleusercontent.com")
os.Setenv("SECRET_KEY", "alongcharachterjumble")
}
var (
googleOauthConfig = &oauth2.Config{
RedirectURL: "http://127.0.0.1:8080/auth", //defined in Google console
ClientID: os.Getenv("CLIENT_ID"),
ClientSecret: os.Getenv("SECRET_KEY"),
Scopes: []string{"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/userinfo.email"},
Endpoint: google.Endpoint,
}
// Some random string, random for each request
oauthStateString = "random"
)
func main() {
http.HandleFunc("/", handleMain)
http.HandleFunc("/GoogleLogin", handleGoogleLogin)
http.HandleFunc("/GoogleCallback", handleGoogleCallback)
fmt.Println(http.ListenAndServe(":8080", nil))
}
func handleMain(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, htmlIndex)
fmt.Println("another request made")
}
func handleGoogleLogin(w http.ResponseWriter, r *http.Request) {
url := googleOauthConfig.AuthCodeURL(oauthStateString)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}
func handleGoogleCallback(w http.ResponseWriter, r *http.Request) {
state := r.FormValue("state")
if state != oauthStateString {
fmt.Printf("invalid oauth state, expected '%s', got '%s'\n", oauthStateString, state)
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}
code := r.FormValue("code")
token, err := googleOauthConfig.Exchange(oauth2.NoContext, code)
if err != nil {
fmt.Println("Code exchange failed with '%s'\n", err)
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}
response, err := http.Get("https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + token.AccessToken)
defer response.Body.Close()
contents, err := ioutil.ReadAll(response.Body)
fmt.Fprintf(w, "Content: %s\n", contents)
}
But I get this error from google:
That’s an error.
Error: invalid_request
Missing required parameter: client_id
Learn more
Request Details client_id= redirect_uri=http://127.0.0.1:8080/auth
response_type=code
scope=https://www.googleapis.com/auth/userinfo.profile
https://www.googleapis.com/auth/userinfo.email state=random
What's wrong here? How can I fix it?
The error message indicates that ClientID is not initialized.
That looks consistent with the code, since var declarations are executed before the init functions.
So when your var requests os.Getenv("CLIENT_ID") the value is blank since init has not executed yet.
From the documentation:
A package with no imports is initialized by assigning initial values to all its package-level variables followed by calling all init functions in the order they appear in the source, possibly in multiple files, as presented to the compiler
https://golang.org/ref/spec#Package_initialization
To fix this, either put the string directly in the var initialization, or trigger the initialization from the init after you set the values.
Like:
var (
googleOauthConfig *oauth2.Config
)
func init() {
// init ENV
// initialize the variable using ENV values
googleOauthConfig = &oauth2.Config{ ... }
}
Alternatively, you can set those ENV values at the OS level before executing the actual Go program.
Hopefully, this is an easy way to earn some rep. This seems very simple, so I must be doing something wrong and just cant see it.
I have a simple middleware which a transaction id and adds it to the request and response headers.
func HandleTransactionID(fn http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
tid := uuid.NewV4()
req.Header.Set(TransIDHeader, TransIDPrefix + tid.String())
w.Header().Set(TransIDHeader, TransIDPrefix + tid.String())
fn(w, req)
}
}
In my unit tests, I've confirmed the response header is successfully set, but it doesn't appear the the request header is being set. I would assume that it is possible to modify the request headers, so ?
const (
WriteTestHeader = "WriterTransHeader"
RequestTestHeader = "ReqTransHeader"
)
func recorderFunc(w http.ResponseWriter, req *http.Request){
w.Header().Set(WriteTestHeader, w.Header().Get(TransIDHeader))
w.Header().Set(RequestTestHeader, req.Header.Get(TransIDHeader))
}
func TestHandleTransactionID(t *testing.T) {
recorder := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/foo", nil)
middleware.HandleTransactionID(recorderFunc)(recorder, req)
if req.Header.Get(RequestTestHeader) == "" {
t.Error("request header is nil")
}
if recorder.Header().Get(WriteTestHeader) == "" {
t.Error("response header is nil")
}
if req.Header.Get(RequestTestHeader) != recorder.Header().Get(WriteTestHeader) {
t.Errorf("header value mismatch: %s != %s",
req.Header.Get(RequestTestHeader),
recorder.Header().Get(WriteTestHeader))
}
}
In your test, req.Header.Get(RequestTestHeader) will always remain an empty string because you are not setting the Key as 'RequestTestHeader' in the request header but in the ResponseWriter w.Header().Set(RequestTestHeader, req.Header.Get(TransIDHeader))
On an unrelated note, It would be considered idomatic Go, to have your middleware function signature using the http.Handler interface, func HandleTransactionID(fn http.Handler) http.Handler.