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={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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,11 +644,13 @@ func (j *JWT) VerifyTokenString(ctx *context.Context, token string, dest interfa
|
|||
}
|
||||
}
|
||||
|
||||
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.
|
||||
tokenInfo := &TokenInfo{
|
||||
|
|
Loading…
Reference in New Issue
Block a user