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 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") } 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) { /* 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) */ // 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" // 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 } /* Custom validation checks can be performed after Verify calls too: currentUserID := "53afcf05-38a3-43c3-82af-8bbbe0e4a149" userID := refresh.Claims.Subject if userID != currentUserID { ctx.StopWithStatus(iris.StatusUnauthorized) return } */ // All OK, re-generate the new pair and send to client. generateTokenPair(ctx, j) }