iris/_examples/auth/jwt/refresh-token/main.go

141 lines
4.6 KiB
Go

package main
import (
"time"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/jwt"
)
// UserClaims a custom access claims structure.
type UserClaims struct {
// In order to separate refresh and access tokens on validation level:
// - Set a different Issuer, with a field of: Issuer string `json:"iss"`
// - Set the Iris JWT's json tag option "required" on an access token field,
// e.g. Username string `json:"username,required"`
// - Let the middleware validate the correct one based on the given MaxAge,
// which should be different between refresh and max age (refersh should be bigger)
// by setting the `jwt.ExpectRefreshToken` on Verify/VerifyToken/VerifyTokenString
// (see `refreshToken` function below)
ID string `json:"user_id"`
Username string `json:"username"`
}
// For refresh token, we will just use the jwt.Claims
// structure which contains the standard JWT fields.
func main() {
app := iris.New()
j := jwt.HMAC(15*time.Minute, "secret", "itsa16bytesecret")
app.Get("/authenticate", func(ctx iris.Context) {
generateTokenPair(ctx, j)
})
app.Get("/refresh", func(ctx iris.Context) {
refreshToken(ctx, j)
})
protectedAPI := app.Party("/protected")
{
protectedAPI.Use(j.Verify(func() interface{} {
return new(UserClaims)
})) // OR j.VerifyToken(ctx, &claims, jwt.MeetRequirements(&UserClaims{}))
protectedAPI.Get("/", func(ctx iris.Context) {
// Get token info, even if our UserClaims does not embed those
// through GetTokenInfo:
expiresAt := jwt.GetTokenInfo(ctx).Claims.Expiry.Time()
// Get your custom JWT claims through Get,
// which is a shortcut of GetTokenInfo(ctx).Value:
claims := jwt.Get(ctx).(*UserClaims)
ctx.Writef("Username: %s\nExpires at: %s\n", claims.Username, expiresAt)
})
}
// http://localhost:8080/protected (401)
// http://localhost:8080/authenticate (200) (response JSON {access_token, refresh_token})
// 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})
// http://localhost:8080/refresh?token={access_token} (401)
app.Listen(":8080")
}
func generateTokenPair(ctx iris.Context, j *jwt.JWT) {
// Simulate a user...
userID := "53afcf05-38a3-43c3-82af-8bbbe0e4a149"
// Map the current user with the refresh token,
// so we make sure, on refresh route, that this refresh token owns
// to that user before re-generate.
refresh := jwt.Claims{Subject: userID}
access := UserClaims{
ID: userID,
Username: "kataras",
}
// Generates a Token Pair, long-live for refresh tokens, e.g. 1 hour.
// Second argument is the refresh claims and,
// the last one is the access token's claims.
tokenPair, err := j.TokenPair(1*time.Hour, refresh, access)
if err != nil {
ctx.Application().Logger().Debugf("token pair: %v", err)
ctx.StopWithStatus(iris.StatusInternalServerError)
return
}
// Send the generated token pair to the client.
// The tokenPair looks like: {"access_token": $token, "refresh_token": $token}
ctx.JSON(tokenPair)
}
func refreshToken(ctx iris.Context, j *jwt.JWT) {
var tokenPair jwt.TokenPair
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
}
}
var refreshClaims jwt.Claims
_, err := j.VerifyTokenString(ctx, tokenPair.RefreshToken, &refreshClaims, jwt.ExpectRefreshToken)
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.
currentUserID := "53afcf05-38a3-43c3-82af-8bbbe0e4a149"
userID := refreshClaims.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)
}