diff --git a/_examples/auth/jwt/refresh-token/main.go b/_examples/auth/jwt/refresh-token/main.go index 7c0629b5..f48c8231 100644 --- a/_examples/auth/jwt/refresh-token/main.go +++ b/_examples/auth/jwt/refresh-token/main.go @@ -60,7 +60,8 @@ func main() { // http://localhost:8080/protected?token={access_token} (200) // http://localhost:8080/protected?token={refresh_token} (401) // http://localhost:8080/refresh?token={refresh_token} - // OR (request JSON{refresh_token = {refresh_token}}) (200) (response JSON {access_token, refresh_token}) + // OR http://localhost:8080/refresh (request JSON{refresh_token = {refresh_token}}) (200) (response JSON {access_token, refresh_token}) + // OR http://localhost:8080/refresh (request PLAIN TEXT of {refresh_token}) (200) (response JSON {access_token, refresh_token}) // http://localhost:8080/refresh?token={access_token} (401) app.Listen(":8080") } @@ -95,45 +96,36 @@ func generateTokenPair(ctx iris.Context, j *jwt.JWT) { } func refreshToken(ctx iris.Context, j *jwt.JWT) { - var tokenPair jwt.TokenPair + /* + We could pass a jwt.Claims pointer as the second argument, + but we don't have to because the method already returns + the standard JWT claims information back to us: + refresh, err := VerifyRefreshToken(ctx, nil) + */ - if token := ctx.URLParam("token"); token != "" { - // Grab the refresh token from the url argument. - tokenPair.RefreshToken = token - } else { - // Otherwise grab the refresh token from a JSON body (you can let it fetch by URL parameter too but - // it's common practice that you read it from a json body as - // it may contain the access token too (the same response we sent on generateTokenPair)). - err := ctx.ReadJSON(&tokenPair) - if err != nil { - ctx.StatusCode(iris.StatusBadRequest) - return - } - } + // Assuming you have access to the current user, e.g. sessions. + // + // Simulate a database call against our jwt subject + // to make sure that this refresh token is a pair generated by this user. + // * Note: You can remove the ExpectSubject and do this validation later on by yourself. + currentUserID := "53afcf05-38a3-43c3-82af-8bbbe0e4a149" - var refreshClaims jwt.Claims - _, err := j.VerifyTokenString(ctx, tokenPair.RefreshToken, &refreshClaims, jwt.ExpectRefreshToken) + // Verify the refresh token, which its subject MUST match the "currentUserID". + _, err := j.VerifyRefreshToken(ctx, nil, jwt.ExpectSubject(currentUserID)) if err != nil { ctx.Application().Logger().Debugf("verify refresh token: %v", err) ctx.StatusCode(iris.StatusUnauthorized) return } - // Assuming you have access to the current user, e.g. sessions. - // - // Simulate a database call against our jwt subject - // to make sure that this refresh token is a pair generated by this user. + /* Custom validation checks can be performed after Verify calls too: currentUserID := "53afcf05-38a3-43c3-82af-8bbbe0e4a149" - - userID := refreshClaims.Subject + userID := refresh.Claims.Subject if userID != currentUserID { ctx.StopWithStatus(iris.StatusUnauthorized) return } - // - // Otherwise, the request must contain the (old) access token too, - // even if it's invalid, we can still fetch its fields, such as the user id. - // [...leave it for you] + */ // All OK, re-generate the new pair and send to client. generateTokenPair(ctx, j) diff --git a/context/context.go b/context/context.go index aa15cbac..f4352ba8 100644 --- a/context/context.go +++ b/context/context.go @@ -2410,7 +2410,28 @@ func (ctx *Context) ReadMsgPack(ptr interface{}) error { // If a GET method request then it reads from a form (or URL Query), otherwise // it tries to match (depending on the request content-type) the data format e.g. // JSON, Protobuf, MsgPack, XML, YAML, MultipartForm and binds the result to the "ptr". +// As a special case if the "ptr" was a pointer to string or []byte +// then it will bind it to the request body as it is. func (ctx *Context) ReadBody(ptr interface{}) error { + + // If the ptr is string or byte, read the body as it's. + switch v := ptr.(type) { + case *string: + b, err := ctx.GetBody() + if err != nil { + return err + } + + *v = string(b) + case *[]byte: + b, err := ctx.GetBody() + if err != nil { + return err + } + + copy(*v, b) + } + if ctx.Method() == http.MethodGet { if ctx.Request().URL.RawQuery != "" { // try read from query. diff --git a/middleware/jwt/jwt.go b/middleware/jwt/jwt.go index 25154bd3..f449383e 100644 --- a/middleware/jwt/jwt.go +++ b/middleware/jwt/jwt.go @@ -395,6 +395,32 @@ func (j *JWT) VerifyToken(ctx *context.Context, claimsPtr interface{}, expectati return j.VerifyTokenString(ctx, token, claimsPtr, expectations...) } +// VerifyRefreshToken like the `VerifyToken` but it verifies a refresh token one instead. +// If the implementation does not fill the application's requirements, +// you can ignore this method and still use the `VerifyToken` for refresh tokens too. +// +// This method adds the ExpectRefreshToken expectation and it +// tries to read the refresh token from raw body or, +// if content type was application/json, then it extracts the token +// from the JSON request body's {"refresh_token": "$token"} key. +func (j *JWT) VerifyRefreshToken(ctx *context.Context, claimsPtr interface{}, expectations ...Expectation) (*TokenInfo, error) { + token := j.RequestToken(ctx) + if token == "" { + var tokenPair TokenPair // read "refresh_token" from JSON. + if ctx.GetContentTypeRequested() == context.ContentJSONHeaderValue { + ctx.ReadJSON(&tokenPair) // ignore error. + token = tokenPair.RefreshToken + if token == "" { + return nil, ErrMissing + } + } else { + ctx.ReadBody(&token) + } + } + + return j.VerifyTokenString(ctx, token, claimsPtr, append(expectations, ExpectRefreshToken)...) +} + // RequestToken extracts the token from the request. func (j *JWT) RequestToken(ctx *context.Context) (token string) { for _, extract := range j.Extractors { @@ -536,10 +562,34 @@ func (j *JWT) VerifyTokenString(ctx *context.Context, token string, dest interfa tokenMaxAger tokenWithMaxAge ) - if err = parsedToken.Claims(j.VerificationKey, dest, &claims, &tokenMaxAger); err != nil { + var ( + ignoreDest = dest == nil + ignoreVarClaims bool + ) + if !ignoreDest { // if dest was not nil, check if the dest is already a standard claims pointer. + _, ignoreVarClaims = dest.(*Claims) + } + + // Ensure read the standard claims one if dest was Claims or was nil. + // (it wont break anything if we unmarshal them twice though, we just do it for performance reasons). + var pointers = []interface{}{&tokenMaxAger} + if !ignoreDest { + pointers = append(pointers, dest) + } + if !ignoreVarClaims { + pointers = append(pointers, &claims) + } + if err = parsedToken.Claims(j.VerificationKey, pointers...); err != nil { return nil, err } + // Set the std claims, if missing from receiver so the expectations and validation still work. + if ignoreVarClaims { + claims = *dest.(*Claims) + } else if ignoreDest { + dest = &claims + } + expectMaxAge := j.MaxAge // Build the Expected value. @@ -594,10 +644,12 @@ func (j *JWT) VerifyTokenString(ctx *context.Context, token string, dest interfa } } - if ut, ok := dest.(TokenSetter); ok { - // The u.Token is empty even if we set it and export it on JSON structure. - // Set it manually. - ut.SetToken(token) + if !ignoreDest { + if ut, ok := dest.(TokenSetter); ok { + // The u.Token is empty even if we set it and export it on JSON structure. + // Set it manually. + ut.SetToken(token) + } } // Set the information.