mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
jwt: add the (last) helper: VerifyRefreshToken
This commit is contained in:
parent
09923183e8
commit
0d73b63b28
|
@ -60,7 +60,8 @@ func main() {
|
||||||
// http://localhost:8080/protected?token={access_token} (200)
|
// http://localhost:8080/protected?token={access_token} (200)
|
||||||
// http://localhost:8080/protected?token={refresh_token} (401)
|
// http://localhost:8080/protected?token={refresh_token} (401)
|
||||||
// http://localhost:8080/refresh?token={refresh_token}
|
// 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)
|
// http://localhost:8080/refresh?token={access_token} (401)
|
||||||
app.Listen(":8080")
|
app.Listen(":8080")
|
||||||
}
|
}
|
||||||
|
@ -95,45 +96,36 @@ func generateTokenPair(ctx iris.Context, j *jwt.JWT) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshToken(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 != "" {
|
// Assuming you have access to the current user, e.g. sessions.
|
||||||
// Grab the refresh token from the url argument.
|
//
|
||||||
tokenPair.RefreshToken = token
|
// Simulate a database call against our jwt subject
|
||||||
} else {
|
// to make sure that this refresh token is a pair generated by this user.
|
||||||
// Otherwise grab the refresh token from a JSON body (you can let it fetch by URL parameter too but
|
// * Note: You can remove the ExpectSubject and do this validation later on by yourself.
|
||||||
// it's common practice that you read it from a json body as
|
currentUserID := "53afcf05-38a3-43c3-82af-8bbbe0e4a149"
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var refreshClaims jwt.Claims
|
// Verify the refresh token, which its subject MUST match the "currentUserID".
|
||||||
_, err := j.VerifyTokenString(ctx, tokenPair.RefreshToken, &refreshClaims, jwt.ExpectRefreshToken)
|
_, err := j.VerifyRefreshToken(ctx, nil, jwt.ExpectSubject(currentUserID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Application().Logger().Debugf("verify refresh token: %v", err)
|
ctx.Application().Logger().Debugf("verify refresh token: %v", err)
|
||||||
ctx.StatusCode(iris.StatusUnauthorized)
|
ctx.StatusCode(iris.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assuming you have access to the current user, e.g. sessions.
|
/* Custom validation checks can be performed after Verify calls too:
|
||||||
//
|
|
||||||
// Simulate a database call against our jwt subject
|
|
||||||
// to make sure that this refresh token is a pair generated by this user.
|
|
||||||
currentUserID := "53afcf05-38a3-43c3-82af-8bbbe0e4a149"
|
currentUserID := "53afcf05-38a3-43c3-82af-8bbbe0e4a149"
|
||||||
|
userID := refresh.Claims.Subject
|
||||||
userID := refreshClaims.Subject
|
|
||||||
if userID != currentUserID {
|
if userID != currentUserID {
|
||||||
ctx.StopWithStatus(iris.StatusUnauthorized)
|
ctx.StopWithStatus(iris.StatusUnauthorized)
|
||||||
return
|
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.
|
// All OK, re-generate the new pair and send to client.
|
||||||
generateTokenPair(ctx, j)
|
generateTokenPair(ctx, j)
|
||||||
|
|
|
@ -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
|
// 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.
|
// 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".
|
// 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 {
|
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.Method() == http.MethodGet {
|
||||||
if ctx.Request().URL.RawQuery != "" {
|
if ctx.Request().URL.RawQuery != "" {
|
||||||
// try read from query.
|
// try read from query.
|
||||||
|
|
|
@ -395,6 +395,32 @@ func (j *JWT) VerifyToken(ctx *context.Context, claimsPtr interface{}, expectati
|
||||||
return j.VerifyTokenString(ctx, token, claimsPtr, expectations...)
|
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.
|
// RequestToken extracts the token from the request.
|
||||||
func (j *JWT) RequestToken(ctx *context.Context) (token string) {
|
func (j *JWT) RequestToken(ctx *context.Context) (token string) {
|
||||||
for _, extract := range j.Extractors {
|
for _, extract := range j.Extractors {
|
||||||
|
@ -536,10 +562,34 @@ func (j *JWT) VerifyTokenString(ctx *context.Context, token string, dest interfa
|
||||||
tokenMaxAger tokenWithMaxAge
|
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
|
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
|
expectMaxAge := j.MaxAge
|
||||||
|
|
||||||
// Build the Expected value.
|
// 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 {
|
if !ignoreDest {
|
||||||
// The u.Token is empty even if we set it and export it on JSON structure.
|
if ut, ok := dest.(TokenSetter); ok {
|
||||||
// Set it manually.
|
// The u.Token is empty even if we set it and export it on JSON structure.
|
||||||
ut.SetToken(token)
|
// Set it manually.
|
||||||
|
ut.SetToken(token)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the information.
|
// Set the information.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user