2020-09-16 12:57:11 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/kataras/iris/v12"
|
|
|
|
"github.com/kataras/iris/v12/middleware/jwt"
|
|
|
|
)
|
|
|
|
|
2020-10-17 05:40:17 +02:00
|
|
|
// UserClaims a custom access claims structure.
|
2020-09-16 12:57:11 +02:00
|
|
|
type UserClaims struct {
|
2020-10-17 05:40:17 +02:00
|
|
|
// 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"`
|
2020-09-16 12:57:11 +02:00
|
|
|
}
|
|
|
|
|
2020-10-17 05:40:17 +02:00
|
|
|
// For refresh token, we will just use the jwt.Claims
|
|
|
|
// structure which contains the standard JWT fields.
|
2020-09-16 12:57:11 +02:00
|
|
|
|
|
|
|
func main() {
|
|
|
|
app := iris.New()
|
|
|
|
|
2020-10-17 05:40:17 +02:00
|
|
|
j := jwt.HMAC(15*time.Minute, "secret", "itsa16bytesecret")
|
2020-09-16 12:57:11 +02:00
|
|
|
|
|
|
|
app.Get("/authenticate", func(ctx iris.Context) {
|
2020-10-17 05:40:17 +02:00
|
|
|
generateTokenPair(ctx, j)
|
2020-09-16 12:57:11 +02:00
|
|
|
})
|
|
|
|
|
2020-10-17 05:40:17 +02:00
|
|
|
app.Get("/refresh_json", func(ctx iris.Context) {
|
|
|
|
refreshTokenFromJSON(ctx, j)
|
2020-09-16 12:57:11 +02:00
|
|
|
})
|
|
|
|
|
2020-10-17 05:40:17 +02:00
|
|
|
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)
|
|
|
|
})
|
|
|
|
}
|
2020-09-16 12:57:11 +02:00
|
|
|
|
2020-10-17 05:40:17 +02:00
|
|
|
// http://localhost:8080/protected (401)
|
2020-09-16 12:57:11 +02:00
|
|
|
// http://localhost:8080/authenticate (200) (response JSON {access_token, refresh_token})
|
2020-10-17 05:40:17 +02:00
|
|
|
// 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})
|
2020-09-16 12:57:11 +02:00
|
|
|
app.Listen(":8080")
|
|
|
|
}
|
|
|
|
|
2020-10-17 05:40:17 +02:00
|
|
|
func generateTokenPair(ctx iris.Context, j *jwt.JWT) {
|
|
|
|
// Simulate a user...
|
|
|
|
userID := "53afcf05-38a3-43c3-82af-8bbbe0e4a149"
|
2020-09-16 12:57:11 +02:00
|
|
|
|
2020-10-17 05:40:17 +02:00
|
|
|
// 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,
|
2020-09-16 12:57:11 +02:00
|
|
|
Username: "kataras",
|
|
|
|
}
|
|
|
|
|
2020-10-17 05:40:17 +02:00
|
|
|
// 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)
|
2020-09-16 12:57:11 +02:00
|
|
|
if err != nil {
|
2020-10-17 05:40:17 +02:00
|
|
|
ctx.Application().Logger().Debugf("token pair: %v", err)
|
|
|
|
ctx.StopWithStatus(iris.StatusInternalServerError)
|
|
|
|
return
|
2020-09-16 12:57:11 +02:00
|
|
|
}
|
|
|
|
|
2020-10-17 05:40:17 +02:00
|
|
|
// 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
|
|
|
|
}
|
2020-09-16 12:57:11 +02:00
|
|
|
|
2020-10-17 05:40:17 +02:00
|
|
|
var refreshClaims jwt.Claims
|
|
|
|
err = j.VerifyTokenString(ctx, tokenPair.RefreshToken, &refreshClaims)
|
2020-09-16 12:57:11 +02:00
|
|
|
if err != nil {
|
2020-10-17 05:40:17 +02:00
|
|
|
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
|
2020-09-16 12:57:11 +02:00
|
|
|
}
|
2020-10-17 05:40:17 +02:00
|
|
|
//
|
|
|
|
// 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]
|
2020-09-16 12:57:11 +02:00
|
|
|
|
2020-10-17 05:40:17 +02:00
|
|
|
// All OK, re-generate the new pair and send to client.
|
|
|
|
generateTokenPair(ctx, j)
|
2020-09-16 12:57:11 +02:00
|
|
|
}
|