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 { // We could that JWT field to separate the access and refresh token: // Issuer string `json:"iss"` // But let's cover the "required" feature too, see below: ID string `json:"user_id,required"` Username string `json:"username,required"` } // 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_json", func(ctx iris.Context) { refreshTokenFromJSON(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_json (request JSON{refresh_token = {refresh_token}}) (200) (response JSON {access_token, refresh_token}) 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 refreshTokenFromJSON(ctx iris.Context, j *jwt.JWT) { var tokenPair jwt.TokenPair // 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) 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) }