mirror of
https://github.com/kataras/iris.git
synced 2025-12-18 10:27:06 +00:00
New JWT features and changes (examples updated). Improvements on the Context User and Private Error features
TODO: Write the new e-book JWT section and the HISTORY entry of the chnages and add a simple example on site docs
This commit is contained in:
@@ -12,11 +12,15 @@ import (
|
||||
)
|
||||
|
||||
type userClaims struct {
|
||||
jwt.Claims
|
||||
Username string
|
||||
// Optionally:
|
||||
Issuer string `json:"iss"`
|
||||
Subject string `json:"sub"`
|
||||
Audience jwt.Audience `json:"aud"`
|
||||
//
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
const testMaxAge = 3 * time.Second
|
||||
const testMaxAge = 7 * time.Second
|
||||
|
||||
// Random RSA verification and encryption.
|
||||
func TestRSA(t *testing.T) {
|
||||
@@ -25,13 +29,13 @@ func TestRSA(t *testing.T) {
|
||||
os.Remove(jwt.DefaultSignFilename)
|
||||
os.Remove(jwt.DefaultEncFilename)
|
||||
})
|
||||
testWriteVerifyToken(t, j)
|
||||
testWriteVerifyBlockToken(t, j)
|
||||
}
|
||||
|
||||
// HMAC verification and encryption.
|
||||
func TestHMAC(t *testing.T) {
|
||||
j := jwt.HMAC(testMaxAge, "secret", "itsa16bytesecret")
|
||||
testWriteVerifyToken(t, j)
|
||||
testWriteVerifyBlockToken(t, j)
|
||||
}
|
||||
|
||||
func TestNew_HMAC(t *testing.T) {
|
||||
@@ -44,7 +48,7 @@ func TestNew_HMAC(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testWriteVerifyToken(t, j)
|
||||
testWriteVerifyBlockToken(t, j)
|
||||
}
|
||||
|
||||
// HMAC verification only (unecrypted).
|
||||
@@ -53,54 +57,60 @@ func TestVerify(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testWriteVerifyToken(t, j)
|
||||
testWriteVerifyBlockToken(t, j)
|
||||
}
|
||||
|
||||
func testWriteVerifyToken(t *testing.T, j *jwt.JWT) {
|
||||
func testWriteVerifyBlockToken(t *testing.T, j *jwt.JWT) {
|
||||
t.Helper()
|
||||
|
||||
j.UseBlocklist()
|
||||
j.Extractors = append(j.Extractors, jwt.FromJSON("access_token"))
|
||||
standardClaims := jwt.Claims{Issuer: "an-issuer", Audience: jwt.Audience{"an-audience"}}
|
||||
expectedClaims := userClaims{
|
||||
Claims: j.Expiry(standardClaims),
|
||||
|
||||
customClaims := &userClaims{
|
||||
Issuer: "an-issuer",
|
||||
Audience: jwt.Audience{"an-audience"},
|
||||
Subject: "user",
|
||||
Username: "kataras",
|
||||
}
|
||||
|
||||
app := iris.New()
|
||||
app.OnErrorCode(iris.StatusUnauthorized, func(ctx iris.Context) {
|
||||
if err := ctx.GetErr(); err != nil {
|
||||
// Test accessing the private error and set this as the response body.
|
||||
ctx.WriteString(err.Error())
|
||||
} else { // Else the default behavior
|
||||
ctx.WriteString(iris.StatusText(iris.StatusUnauthorized))
|
||||
}
|
||||
})
|
||||
|
||||
app.Get("/auth", func(ctx iris.Context) {
|
||||
j.WriteToken(ctx, expectedClaims)
|
||||
j.WriteToken(ctx, customClaims)
|
||||
})
|
||||
|
||||
app.Post("/restricted", func(ctx iris.Context) {
|
||||
app.Post("/protected", func(ctx iris.Context) {
|
||||
var claims userClaims
|
||||
if err := j.VerifyToken(ctx, &claims); err != nil {
|
||||
ctx.StopWithStatus(iris.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(claims)
|
||||
})
|
||||
|
||||
app.Post("/restricted_middleware_readclaims", j.Verify, func(ctx iris.Context) {
|
||||
var claims userClaims
|
||||
if err := jwt.ReadClaims(ctx, &claims); err != nil {
|
||||
ctx.StopWithStatus(iris.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(claims)
|
||||
})
|
||||
|
||||
app.Post("/restricted_middleware_get", j.Verify, func(ctx iris.Context) {
|
||||
claims, err := jwt.Get(ctx)
|
||||
_, err := j.VerifyToken(ctx, &claims)
|
||||
if err != nil {
|
||||
ctx.StopWithStatus(iris.StatusUnauthorized)
|
||||
// t.Logf("%s: %v", ctx.Path(), err)
|
||||
ctx.StopWithError(iris.StatusUnauthorized, iris.PrivateError(err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(claims)
|
||||
})
|
||||
|
||||
m := app.Party("/middleware")
|
||||
m.Use(j.Verify(func() interface{} {
|
||||
return new(userClaims)
|
||||
}))
|
||||
m.Post("/protected", func(ctx iris.Context) {
|
||||
claims := jwt.Get(ctx)
|
||||
ctx.JSON(claims)
|
||||
})
|
||||
m.Post("/invalidate", func(ctx iris.Context) {
|
||||
ctx.Logout() // OR j.Invalidate(ctx)
|
||||
})
|
||||
|
||||
e := httptest.New(t, app)
|
||||
|
||||
// Get token.
|
||||
@@ -109,31 +119,186 @@ func testWriteVerifyToken(t *testing.T, j *jwt.JWT) {
|
||||
t.Fatalf("empty token")
|
||||
}
|
||||
|
||||
restrictedPaths := [...]string{"/restricted", "/restricted_middleware_readclaims", "/restricted_middleware_get"}
|
||||
restrictedPaths := [...]string{"/protected", "/middleware/protected"}
|
||||
|
||||
now := time.Now()
|
||||
for _, path := range restrictedPaths {
|
||||
// Authorization Header.
|
||||
e.POST(path).WithHeader("Authorization", "Bearer "+rawToken).Expect().
|
||||
Status(httptest.StatusOK).JSON().Equal(expectedClaims)
|
||||
Status(httptest.StatusOK).JSON().Equal(customClaims)
|
||||
|
||||
// URL Query.
|
||||
e.POST(path).WithQuery("token", rawToken).Expect().
|
||||
Status(httptest.StatusOK).JSON().Equal(expectedClaims)
|
||||
Status(httptest.StatusOK).JSON().Equal(customClaims)
|
||||
|
||||
// JSON Body.
|
||||
e.POST(path).WithJSON(iris.Map{"access_token": rawToken}).Expect().
|
||||
Status(httptest.StatusOK).JSON().Equal(expectedClaims)
|
||||
Status(httptest.StatusOK).JSON().Equal(customClaims)
|
||||
|
||||
// Missing "Bearer".
|
||||
e.POST(path).WithHeader("Authorization", rawToken).Expect().
|
||||
Status(httptest.StatusUnauthorized)
|
||||
Status(httptest.StatusUnauthorized).Body().Equal("token is missing")
|
||||
}
|
||||
|
||||
// Invalidate the token.
|
||||
e.POST("/middleware/invalidate").WithQuery("token", rawToken).Expect().
|
||||
Status(httptest.StatusOK)
|
||||
// Token is blocked by server.
|
||||
e.POST("/middleware/protected").WithQuery("token", rawToken).Expect().
|
||||
Status(httptest.StatusUnauthorized).Body().Equal("token is blocked")
|
||||
|
||||
expireRemDur := testMaxAge - time.Since(now)
|
||||
|
||||
// Expiration.
|
||||
time.Sleep(expireRemDur /* -end */)
|
||||
for _, path := range restrictedPaths {
|
||||
e.POST(path).WithQuery("token", rawToken).Expect().Status(httptest.StatusUnauthorized)
|
||||
e.POST(path).WithQuery("token", rawToken).Expect().
|
||||
Status(httptest.StatusUnauthorized).Body().Equal("token is expired (exp)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyMap(t *testing.T) {
|
||||
j := jwt.HMAC(testMaxAge, "secret", "itsa16bytesecret")
|
||||
expectedClaims := iris.Map{
|
||||
"iss": "tester",
|
||||
"username": "makis",
|
||||
"roles": []string{"admin"},
|
||||
}
|
||||
|
||||
app := iris.New()
|
||||
app.Get("/user/auth", func(ctx iris.Context) {
|
||||
err := j.WriteToken(ctx, expectedClaims)
|
||||
if err != nil {
|
||||
ctx.StopWithError(iris.StatusUnauthorized, err)
|
||||
return
|
||||
}
|
||||
|
||||
if expectedClaims["exp"] == nil || expectedClaims["iat"] == nil {
|
||||
ctx.StopWithText(iris.StatusBadRequest,
|
||||
"exp or/and iat is nil - this means that the expiry was not set")
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
userAPI := app.Party("/user")
|
||||
userAPI.Post("/", func(ctx iris.Context) {
|
||||
var claims iris.Map
|
||||
if _, err := j.VerifyToken(ctx, &claims); err != nil {
|
||||
ctx.StopWithError(iris.StatusUnauthorized, iris.PrivateError(err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(claims)
|
||||
})
|
||||
|
||||
// Test map + Verify middleware.
|
||||
userAPI.Post("/middleware", j.Verify(func() interface{} {
|
||||
return &iris.Map{} // or &map[string]interface{}{}
|
||||
}), func(ctx iris.Context) {
|
||||
claims := jwt.Get(ctx)
|
||||
ctx.JSON(claims)
|
||||
})
|
||||
|
||||
e := httptest.New(t, app, httptest.LogLevel("error"))
|
||||
token := e.GET("/user/auth").Expect().Status(httptest.StatusOK).Body().Raw()
|
||||
if token == "" {
|
||||
t.Fatalf("empty token")
|
||||
}
|
||||
|
||||
e.POST("/user").WithHeader("Authorization", "Bearer "+token).Expect().
|
||||
Status(httptest.StatusOK).JSON().Equal(expectedClaims)
|
||||
|
||||
e.POST("/user/middleware").WithHeader("Authorization", "Bearer "+token).Expect().
|
||||
Status(httptest.StatusOK).JSON().Equal(expectedClaims)
|
||||
|
||||
e.POST("/user").Expect().Status(httptest.StatusUnauthorized)
|
||||
}
|
||||
|
||||
type customClaims struct {
|
||||
Username string `json:"username"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
func (c *customClaims) SetToken(tok string) {
|
||||
c.Token = tok
|
||||
}
|
||||
|
||||
func TestVerifyStruct(t *testing.T) {
|
||||
maxAge := testMaxAge / 2
|
||||
j := jwt.HMAC(maxAge, "secret", "itsa16bytesecret")
|
||||
|
||||
app := iris.New()
|
||||
app.Get("/user/auth", func(ctx iris.Context) {
|
||||
err := j.WriteToken(ctx, customClaims{
|
||||
Username: "makis",
|
||||
})
|
||||
if err != nil {
|
||||
ctx.StopWithError(iris.StatusUnauthorized, err)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
userAPI := app.Party("/user")
|
||||
userAPI.Post("/", func(ctx iris.Context) {
|
||||
var claims customClaims
|
||||
if _, err := j.VerifyToken(ctx, &claims); err != nil {
|
||||
ctx.StopWithError(iris.StatusUnauthorized, iris.PrivateError(err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(claims)
|
||||
})
|
||||
|
||||
e := httptest.New(t, app)
|
||||
token := e.GET("/user/auth").Expect().Status(httptest.StatusOK).Body().Raw()
|
||||
if token == "" {
|
||||
t.Fatalf("empty token")
|
||||
}
|
||||
e.POST("/user").WithHeader("Authorization", "Bearer "+token).Expect().
|
||||
Status(httptest.StatusOK).JSON().Object().ContainsMap(iris.Map{
|
||||
"username": "makis",
|
||||
"token": token, // Test SetToken.
|
||||
})
|
||||
|
||||
e.POST("/user").Expect().Status(httptest.StatusUnauthorized)
|
||||
time.Sleep(maxAge)
|
||||
e.POST("/user").WithHeader("Authorization", "Bearer "+token).Expect().Status(httptest.StatusUnauthorized)
|
||||
}
|
||||
|
||||
func TestVerifyUserAndExpected(t *testing.T) { // Tests the jwt.User struct + context validator + expected.
|
||||
maxAge := testMaxAge / 2
|
||||
j := jwt.HMAC(maxAge, "secret", "itsa16bytesecret")
|
||||
expectedUser := j.NewUser(jwt.Username("makis"), jwt.Roles("admin"), jwt.Fields(iris.Map{
|
||||
"custom": true,
|
||||
})) // only for the sake of the test, we iniitalize it here.
|
||||
expectedUser.Issuer = "tester"
|
||||
|
||||
app := iris.New()
|
||||
app.Get("/user/auth", func(ctx iris.Context) {
|
||||
tok, err := expectedUser.GetToken()
|
||||
if err != nil {
|
||||
ctx.StopWithError(iris.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
ctx.WriteString(tok)
|
||||
})
|
||||
|
||||
userAPI := app.Party("/user")
|
||||
userAPI.Use(jwt.WithExpected(jwt.Expected{Issuer: "tester"}, j.VerifyUser()))
|
||||
userAPI.Post("/", func(ctx iris.Context) {
|
||||
user := ctx.User()
|
||||
ctx.JSON(user)
|
||||
})
|
||||
|
||||
e := httptest.New(t, app)
|
||||
token := e.GET("/user/auth").Expect().Status(httptest.StatusOK).Body().Raw()
|
||||
if token == "" {
|
||||
t.Fatalf("empty token")
|
||||
}
|
||||
|
||||
e.POST("/user").WithHeader("Authorization", "Bearer "+token).Expect().
|
||||
Status(httptest.StatusOK).JSON().Equal(expectedUser)
|
||||
|
||||
// Test generic client message if we don't manage the private error by ourselves.
|
||||
e.POST("/user").Expect().Status(httptest.StatusUnauthorized).Body().Equal("Unauthorized")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user