func Login(c echo.Context) error {
user := &users.User{}
if err := c.Bind(&user); err != nil {
return err
}
return token.SigIn(c, user.Email, user.Password)
}
This is my Login function that retrieve the token when the user send the requests.
the Signin func that handle the token
func SigIn(c echo.Context, email, password string) error {
user := users.User{}
db := database.SetUp()
if err := db.Where("email = ?", email).First(&user).Error; gorm.IsRecordNotFoundError(err) {
restErr := errors.NewBadRequestError("Invalid credentials")
return c.JSON(http.StatusBadRequest, restErr)
}
if user.VerifyPassword(password) != nil {
restErr := errors.NewUnauthorizedError("Couldn't log you in with these credentials")
return c.JSON(http.StatusUnauthorized, restErr)
}
//user is successfull
return CreateToken(c)
}
the CreateToken func is as follow
type TokenJWT struct {
Token string `json:"token"`
}
func CreateToken(c echo.Context) error {
token := jwt.New(jwt.SigningMethodHS256)
claims := token.Claims.(jwt.MapClaims)
claims["authorized"] = true
claims["name"] = "Pascal Gaetan"
claims["exp"] = time.Now().Add(time.Hour * 1).Unix()
// Generate encoded token and send it as response.
t, err := token.SignedString([]byte("my_secret_key"))
if err != nil {
return err
}
return c.JSON(http.StatusOK, TokenJWT{
Token: t,
})
}
when everyhting is succesfull, i would like to get the authenticated user through an URL /api/me that calls a Me function
Let me split your question into two parts: the first one is how to easily encode and decode user in or from JWT token and the second part is how to write a generic code which can retrieve user from everywhere.
From your example I mentioned that you created a MapClaims but to reduce parsing complexity it will be better to create a token using a custom claims type. If you are using dgrijalva/jwt-go, then according to documentation you can do something like that
type UserClaims struct {
Name string `json:"name"`
jwt.StandardClaims
}
// encode it as before, but with your created type
t := jwt.New(signer)
userClaims := &UserClaims{Name: "Burmese"}
t.Claims = userClaims
tokenString, err = t.SignedString(]byte("my_secret_key"))
then you can parse your user in your router/framework middleware with
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c"
token, err := jwt.ParseWithClaims(tokenString, &UserClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte("my_secret_key"), nil
})
if claims, ok := token.Claims.(*UserClaims); ok && token.Valid {
fmt.Printf("%v %v", claims.Name, claims.StandardClaims.ExpiresAt)
} else {
fmt.Println(err)
}
This example was adopted from an official documentation here
Now you know how to parse authenticated user struct with ease and the next logic move is to wrap it into your middleware. Whether there are a lot of implementation details like you can retrieve JWT from cookie, header or query, also defining some ordering on them, the gist the following: you should have wrapped abovementioned code into your middleware and after parsing the struct you can pass it via your request context. I don't use echo and other frameworks, but for pure net/http you can pass your parsed struct from middleware with
context.WithValue(ctx, UserCtxKey, claims)
Hope it helps!
This is a fairly common design pattern to create an authenticated client and then call various action methods on it. You could do something like the following:
type Client struct {
... // other members
token string // unexported unless there is a special reason to do otherwise
}
func NewClient(c echo.Context, email, password string) (*Client, error) {
user := users.User{}
cl := Client{}
... // your original method
cl.token = token
return &cl, nil
}
func (c *Client) DoSomething(...) ... { ... }
Related
i receive a response body "bad request" with "httptest.Client().Postform"
type testServer struct {
*httptest.Server
}
func newTestServer(t *testing.T, h http.Handler) *testServer {
ts := httptest.NewTLSServer(h)
jar, err := cookiejar.New(nil)
if err != nil {
t.Fatal(err)
}
ts.Client().Jar = jar
ts.Client().CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
return &testServer{ts}
}
func (ts *testServer) postForm(t *testing.T, urlPath string, form url.Values) (int, http.Header, string) {
rs, err := ts.Client().PostForm(ts.URL+urlPath, form)
if err != nil {
t.Fatal(err)
}
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
return rs.StatusCode, rs.Header, string(body)
}
I don't know where is the problem, i have also verified the url it's correct.
Always badrequest with POST but with GET request it's works fine.
this is the handler object :
func (app *application) routes() http.Handler {
router := httprouter.New()
router.NotFound = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
app.notFound(w)
})
dynamic := alice.New(app.sessionManager.LoadAndSave, noSurf, app.Authenticated)
router.Handler(http.MethodGet, "/", dynamic.ThenFunc(app.home))
router.Handler(http.MethodGet, "/user/signup", dynamic.ThenFunc(app.userSignup))
router.Handler(http.MethodPost, "/user/signup", dynamic.ThenFunc(app.userSignupPost))
standart := alice.New(app.recoverPanic, app.logRequest, securityHeaders)
return standart.Then(router)
}
the test function :https://go.dev/play/p/k45-JYTYCOS
the app.userSignupPost:
func (app *application) userSignupPost(w http.ResponseWriter, r *http.Request) {
var form userSignupForm
err := app.decodPostForm(r, &form)
if err != nil {
app.clientError(w, http.StatusBadRequest)
return
}
form.CheckField(validator.NotBlank(form.Name), "name", "this field must not be blank")
form.CheckField(validator.NotBlank(form.Email), "email", "this field must not be blank")
form.CheckField(validator.Matches(form.Email, validator.EmailRX), "email", "this field must be a valid email address")
form.CheckField(validator.NotBlank(form.Password), "password", "this field must not be blank")
form.CheckField(validator.MinChars(form.Password, 8), "password", "password must bee at least 8 caracter long")
if !form.Valid() {
data := app.newTemplateData(r)
data.Form = form
app.render(w, http.StatusUnprocessableEntity, "signup.tmpl.html", data)
return
}
err = app.users.Insert(form.Name, form.Email, form.Password)
if err != nil {
if errors.Is(err, models.ErrDuplicateEmail) {
form.AddFieldError("email", "Email already exist")
data := app.newTemplateData(r)
data.Form = form
app.render(w, http.StatusUnprocessableEntity, "signup.tmpl.html", data)
} else {
fmt.Println("error user postform")
app.serverError(w, err)
}
return
}
app.sessionManager.Put(r.Context(), "flash", "Signup Successful. Please log in")
http.Redirect(w, r, "/user/login", http.StatusSeeOther)
}
It appears that you're using https://github.com/justinas/alice to register handlers - you don't want to do this. That package is for middleware chaining - e.g. "before all requests to this URL, first authenticate the request" - you'd put the authentication into a middleware function and then add it to the chain.
So every POST /user/signup request is getting passed first to app.userSignup() (what you are using to handle GET requests). This is because calling alice.ThenFunc() appends the passed handler to the chain and then returns the entire chain as a handler - you need to read this part of the Alice docs carefully before using it.
Replace this line:
router.Handler(http.MethodPost, "/user/signup", dynamic.ThenFunc(app.userSignupPost))
with
router.Handler(http.MethodPost, "/user/signup", http.HandlerFunc(app.userSignupPost))
You may not need the additional decoration of http.HandlerFunc() - try it with and without to see what works. I cannot say for sure without knowing what the body of app.userSignupPost() looks like (same for the other handler functions as well).
You'll then need to do the same for the other handler registration lines - you shouldn't be using middleware chaining for your end handlers. An http.Handler is used for saying, "send any request to path /PP/ppp with method XXXX to this function." Middleware chaining is for preprocessing (authentication, authorization, etc.) - a whole host of things can be done there, but end request handling shouldn't be one of them.
I'm still curious if your use of PostForm() is going to cause you issues for the reason I cited in my comment on your question - try a raw Post() and see if the behavior differs, but after refactoring to take out the alice goop (at least temporarily). When testing a handler, I'd start off with a much more minimal approach - test that the handler itself works before muddying the waters with both alice and what looks like this package.
I think i found the problem , the session cookie are not the same for get and post request. i don't know why it has changed.They use the same http.Client()
I am trying to ensure the body of a post request for example contains exact structure of the body and if not ahould throw an error
for example i have the following function
func UpdatePassword(c *fiber.Ctx) error {
type UpdatePasswordData struct {
Password string `json:"password" form:"password"`
NewPassword string `json:"new_password" form:"new_password"`
NewPasswordConfirm string `json:"new_password_confirm" form:"new_password_confirm"`
}
data := UpdatePasswordData{}
if err := c.BodyParser(&data); err != nil {
return err
}
var user models.User
if data.NewPassword != data.NewPasswordConfirm {
c.Status(400)
return c.JSON(fiber.Map{
"message": "passwords do not match",
})
}
email, _ := middlewares.GetUserEmail(c)
newPassword := models.HashPassword(data.NewPassword)
database.DB.Model(&user).Select("Password").Where("email = ?", email).Updates(map[string]interface{}{"Password": newPassword})
return c.JSON(user)
}
the POST request should be looking for body with this structure
{
"password": "oldpassword",
"new_password": "newpassword",
"new_password_confirm": "newpassword",
}
but currently this endpoint accepts body that does not have this exact structure. So how do i enforce the structure in the body of request, so that if structure does not match, i throw an error?
do not like gin, fiber has not builtin validate package
use go-playground/validator
go get github.com/go-playground/validator
example
type UpdatePasswordData struct {
Password string `json:"password" validate:"required,min=8,max=32"`
NewPassword string `json:"new_password" validate:"required,min=8,max=32"`
NewPasswordConfirm string `json:"new_password_confirm" validate:"eqfield=NewPassword"`
}
func UpdatePassword(c *fiber.Ctx) error {
var body UpdatePasswordData
if err := c.BodyParser(&body); err != nil {
return err
}
validate := validator.New()
if err := validate.Struct(body); err != nil {
return err
}
// do others
// get current user, check password == hash(body.password)
// save new passworld
}
or you can see fiber office docs https://docs.gofiber.io/guide/validation#validator-package
We can use struct tag
`validate:"required"`
to ensure that all the mandatory fields are there in the request payload.
Moreover we can validate the fields with the provided tags of the validator package and for additional validations we can implement custom validators and register them like this:
validate := validator.New()
validate.RegisterValidation("password-validator", PasswordValidator)
I am trying to implement JWT for REST
I'm just curious on this code below
var mySigningKey = []byte("mysecret")
token, err := jwt.Parse(r.Header["Token"][0], func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("There was an error")
}
return mySigningKey, nil
})
fmt.Println(token)
I expect token will return mySigningKey value but its return Token value
that shoud return value inside mySigningKey right?
Or did I misunderstand some concept?
By calling Parse method on your header Token value you are creating the token struct. If you want to get your SigningString you need to call SigningString() method on your token variable.
// Generate the signing string. This is the
// most expensive part of the whole deal. Unless you
// need this for something special, just go straight for
// the SignedString.
func (t *Token) SigningString() (string, error) {
var err error
parts := make([]string, 2)
for i, _ := range parts {
var source map[string]interface{}
if i == 0 {
source = t.Header
} else {
source = t.Claims
}
var jsonValue []byte
if jsonValue, err = json.Marshal(source); err != nil {
return "", err
}
parts[i] = EncodeSegment(jsonValue)
}
return strings.Join(parts, "."), nil
}
The function which is the second argument to Parse is responsible for providing the signing key which will be used to verify the JWT signature is correct.
In the case illustrated in the question, the signing key is a constant because HMAC is used to sign it.
However, if you had a RSA keypair, the token header would be useful to pick the right public key to check the signature, perhaps pulling it down from a well known JWKS endpoint.
I'm working with framework GIN and Gin-JWT in Golang.
So far so good, I was able to authorize and authenticate my REST API with JWT following the example in Gin-JWT package.
I'm trying now to implement some kind of Role in my API.
The flow would be:
Login and auth
Create the JWT with inside the userID and the RoleID
When I call a REST API I confront the role associated to the API with the RoleID in JWT to authorized
So far I have this in my main:
jwtAfp := InitJwtMiddleware(db)
afb := r.Group("api/v1/afb")
afb.Use(jwtAfp.MiddlewareFunc())
afb.GET("/ping", afbController.Ping)
and this for the InitJwtMiddleware using Gin-JWT
func InitJwtMiddleware(db *gorm.DB) *jwt.GinJWTMiddleware {
return &jwt.GinJWTMiddleware{
Realm: "afb",
Key: []byte("secret pwd"),
Timeout: time.Hour,
MaxRefresh: time.Hour,
PayloadFunc: func(data interface{}) jwt.MapClaims {
if v, ok := data.(*model.User); ok {
return jwt.MapClaims{
"afb": v.ID,
}
}
return jwt.MapClaims{}
},
Authenticator: func(c *gin.Context) (interface{}, error) {
var loginVals login
if err := c.Bind(&loginVals); err != nil {
return "", jwt.ErrMissingLoginValues
}
email := loginVals.Username
password := loginVals.Password
var u model.User
db.Where("email = ?", email).First(&u)
if service.CheckPasswordHash(password, u.Password) {
return &u, nil
}
return nil, jwt.ErrFailedAuthentication
},
Authorizator: func(data interface{}, c *gin.Context) bool {
claims := jwt.ExtractClaims(c)
v, ok := data.(float64)
if ok && v == claims["afb"] {
return true
}
return false
},
Unauthorized: func(c *gin.Context, code int, message string) {
c.JSON(code, gin.H{
"code": code,
"message": message,
})
},
TokenHeadName: "Bearer",
TimeFunc: time.Now,
}
}
I would like to add the checking on the Role in the Authorizator section but I'm struggling on how i can do this.
I come up with passing in the InitJwtMiddleware(db) function also the role, this will work but I don't like the idea to "instaziate" a GinJWTMiddleware for each ROLE/API. Or if I could know inside the middleware which function (controller) will be called later I can then figure out if authorize or not. But even this solutin sound awkward to me. I think there will be a most elegant solution, any ideas?
You can try this:
https://github.com/kyfk/gin-jwt
It's the simplest auth[orization/entication] library.
The VerifyPerm function could be helpful for role management.
There's a complete example
func main() {
auth, err := jwt.New(jwt.Auth{
SecretKey: []byte("must change here"),
// Authenticator authenticates a request and return jwt.MapClaims
// that contains a user information of the request.
Authenticator: func(c *gin.Context) (jwt.MapClaims, error) {
var loginForm LoginForm
if err := c.ShouldBind(&loginForm); err != nil {
return nil, jwt.ErrorAuthenticationFailed
}
u, ok := authenticate(req.Username, req.Password)
if ok {
return nil, jwt.ErrorAuthenticationFailed
}
return jwt.MapClaims{
"username": u.Username,
"role": u.Role,
}, nil
},
// UserFetcher takes a jwt.MapClaims and return a user object.
UserFetcher: func(c *gin.Context, claims jwt.MapClaims) (interface{}, error) {
username, ok := claims["username"].(string)
if !ok {
return nil, nil
}
return findByUsername(username)
},
})
// some lines
e.Use(jwt.ErrorHandler)
// issue authorization token
e.POST("/login", auth.AuthenticateHandler)
// refresh token expiration
e.POST("/auth/refresh_token", auth.RefreshHandler)
// role management
e.GET("/operator/hello", Operator(auth), SayHello) // this is only for Operator
e.GET("/admin/hello", Admin(auth), SayHello) // this is only for Admin
}
func Operator(m jwt.Auth) gin.HandlerFunc {
return m.VerifyPerm(func(claims jwt.MapClaims) bool {
return role(claims).IsOperator()
})
}
func Admin(m jwt.Auth) gin.HandlerFunc {
return m.VerifyPerm(func(claims jwt.MapClaims) bool {
return role(claims).IsAdmin()
})
}
I have REST services:
each request has a header with JWT token
each controller get parameters from request (variables, body..) and pass them to data layer
I need to pass JWT token from header of each request into corresponding data layer method like this:
func (a *App) UpdateOrder(_ http.ResponseWriter, r *http.Request) (interface{}, error) {
bodyData := new(models.Order)
err = json.NewDecoder(r.Body).Decode(&bodyData)
if err != nil {
return nil, err
}
user, err := a.Saga.GetUserByToken(r.Header.Get("Authorization")) // here
// error handling ...
a.DbLayer.UpdateOrder(id, bodyData, user) // and there
}
In this case I must write the same code for each controller to get the user by token, and pass this user to database layer explicitly.
Is there a way to pass this user for each request without writing this code in each controller ?
I know about middleware and I can get user by token in my middleware. But how can I pass this user from middleware to corresponding database level method ?
May be I am looking for something like "global variables" for goroutine ? I can get user in my middleware and set it to something like "global variable". I can get the value of this "global variable" in the database layer. But it must be "global variable" for the current web request and concurrent web requests mustn't affect to each other.
Is there a some mechanism in Go, http module or gorilla\mux to implement what I have called "global variables" ?
You are describing contexts.
Originally there was the gorilla context package, which provides a pseudoglobal context object - essentially a map[interface{}]interface{} with a reference intrinsicly available to all players in the middleware/controller/datalayer stack.
See this except from an excellent guide to the package (all credit to the author, Matt Silverlock).
type contextKey int
// Define keys that support equality.
const csrfKey contextKey = 0
const userKey contextKey = 1
var ErrCSRFTokenNotPresent = errors.New("CSRF token not present in the request context.")
// We'll need a helper function like this for every key:type
// combination we store in our context map else we repeat this
// in every middleware/handler that needs to access the value.
func GetCSRFToken(r *http.Request) (string, error) {
val, ok := context.GetOk(r, csrfKey)
if !ok {
return "", ErrCSRFTokenNotPresent
}
token, ok := val.(string)
if !ok {
return "", ErrCSRFTokenNotPresent
}
return token, nil
}
// A bare-bones example
func CSRFMiddleware(h http.Handler) http.Handler {
return func(w http.ResponseWriter, r *http.Request) {
token, err := GetCSRFToken(r)
if err != nil {
http.Error(w, "No good!", http.StatusInternalServerError)
return
}
// The map is global, so we just call the Set function
context.Set(r, csrfKey, token)
h.ServeHTTP(w, r)
}
}
After the gorilla package's inception, a context package was added to the standard library. It's slightly different, in that contexts are no longer pseudoglobal, but instead passed from method to method. Under this, the context comes attached to the initial request - available via request.Context. Layers below the handler can accept a context value as a part of their signature, and read values from it.
Here's a simplified example:
type contextKey string
var (
aPreSharedKey = contextKey("a-preshared-key")
)
func someHandler(w http.ResponseWriter, req *http.Request) {
ctx := context.WithValue(req.Context, aPreSharedKey, req.Header.Get("required-header"))
data, err := someDataLayerFunction(ctx)
if err != nil {
fmt.Fprintf(w, "uhoh", http.StatusBadRequest)
return
}
fmt.Fprintf(w, data, http.StatusOK)
}
func someDataLayerFunction(ctx context.Context) (string, error) {
val, ok := ctx.Value(aPreSharedKey).(string)
if !ok {
return nil, errors.New("required context value missing")
}
return val
}
For more details and a less contrived example, check out google's excellent blog on the context package's use.