2020-05-27 11:02:17 +02:00
|
|
|
// Package jwt_test contains simple Iris jwt tests. Most of the jwt functionality is already tested inside the jose package itself.
|
|
|
|
package jwt_test
|
|
|
|
|
|
|
|
import (
|
2020-05-31 16:57:30 +02:00
|
|
|
"os"
|
2020-05-27 11:02:17 +02:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/kataras/iris/v12"
|
|
|
|
"github.com/kataras/iris/v12/httptest"
|
|
|
|
"github.com/kataras/iris/v12/middleware/jwt"
|
|
|
|
)
|
|
|
|
|
|
|
|
type userClaims struct {
|
2020-10-17 05:40:17 +02:00
|
|
|
// Optionally:
|
|
|
|
Issuer string `json:"iss"`
|
|
|
|
Subject string `json:"sub"`
|
|
|
|
Audience jwt.Audience `json:"aud"`
|
|
|
|
//
|
|
|
|
Username string `json:"username"`
|
2020-05-27 11:02:17 +02:00
|
|
|
}
|
|
|
|
|
2020-10-17 05:40:17 +02:00
|
|
|
const testMaxAge = 7 * time.Second
|
2020-05-27 11:02:17 +02:00
|
|
|
|
|
|
|
// Random RSA verification and encryption.
|
2020-06-07 14:26:06 +02:00
|
|
|
func TestRSA(t *testing.T) {
|
|
|
|
j := jwt.RSA(testMaxAge)
|
2020-05-31 16:57:30 +02:00
|
|
|
t.Cleanup(func() {
|
2020-06-07 14:26:06 +02:00
|
|
|
os.Remove(jwt.DefaultSignFilename)
|
|
|
|
os.Remove(jwt.DefaultEncFilename)
|
2020-05-31 16:57:30 +02:00
|
|
|
})
|
2020-10-17 05:40:17 +02:00
|
|
|
testWriteVerifyBlockToken(t, j)
|
2020-05-27 11:02:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// HMAC verification and encryption.
|
2020-06-07 14:26:06 +02:00
|
|
|
func TestHMAC(t *testing.T) {
|
|
|
|
j := jwt.HMAC(testMaxAge, "secret", "itsa16bytesecret")
|
2020-10-17 05:40:17 +02:00
|
|
|
testWriteVerifyBlockToken(t, j)
|
2020-05-31 16:57:30 +02:00
|
|
|
}
|
|
|
|
|
2020-06-07 14:26:06 +02:00
|
|
|
func TestNew_HMAC(t *testing.T) {
|
2020-05-27 11:02:17 +02:00
|
|
|
j, err := jwt.New(testMaxAge, jwt.HS256, []byte("secret"))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
err = j.WithEncryption(jwt.A128GCM, jwt.DIRECT, []byte("itsa16bytesecret"))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2020-10-17 05:40:17 +02:00
|
|
|
testWriteVerifyBlockToken(t, j)
|
2020-05-27 11:02:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// HMAC verification only (unecrypted).
|
|
|
|
func TestVerify(t *testing.T) {
|
|
|
|
j, err := jwt.New(testMaxAge, jwt.HS256, []byte("another secret"))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2020-10-17 05:40:17 +02:00
|
|
|
testWriteVerifyBlockToken(t, j)
|
2020-05-27 11:02:17 +02:00
|
|
|
}
|
|
|
|
|
2020-10-17 05:40:17 +02:00
|
|
|
func testWriteVerifyBlockToken(t *testing.T, j *jwt.JWT) {
|
2020-05-27 11:02:17 +02:00
|
|
|
t.Helper()
|
|
|
|
|
2020-10-17 05:40:17 +02:00
|
|
|
j.UseBlocklist()
|
2020-05-27 11:02:17 +02:00
|
|
|
j.Extractors = append(j.Extractors, jwt.FromJSON("access_token"))
|
2020-10-17 05:40:17 +02:00
|
|
|
|
|
|
|
customClaims := &userClaims{
|
|
|
|
Issuer: "an-issuer",
|
|
|
|
Audience: jwt.Audience{"an-audience"},
|
|
|
|
Subject: "user",
|
2020-05-27 11:02:17 +02:00
|
|
|
Username: "kataras",
|
|
|
|
}
|
|
|
|
|
|
|
|
app := iris.New()
|
2020-10-17 05:40:17 +02:00
|
|
|
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))
|
2020-05-27 11:02:17 +02:00
|
|
|
}
|
2020-10-17 05:40:17 +02:00
|
|
|
})
|
2020-05-27 11:02:17 +02:00
|
|
|
|
2020-10-17 05:40:17 +02:00
|
|
|
app.Get("/auth", func(ctx iris.Context) {
|
|
|
|
j.WriteToken(ctx, customClaims)
|
2020-05-27 11:02:17 +02:00
|
|
|
})
|
|
|
|
|
2020-10-17 05:40:17 +02:00
|
|
|
app.Post("/protected", func(ctx iris.Context) {
|
2020-05-27 11:02:17 +02:00
|
|
|
var claims userClaims
|
2020-10-17 05:40:17 +02:00
|
|
|
_, err := j.VerifyToken(ctx, &claims)
|
|
|
|
if err != nil {
|
|
|
|
// t.Logf("%s: %v", ctx.Path(), err)
|
|
|
|
ctx.StopWithError(iris.StatusUnauthorized, iris.PrivateError(err))
|
2020-05-27 11:02:17 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.JSON(claims)
|
|
|
|
})
|
|
|
|
|
2020-10-17 05:40:17 +02:00
|
|
|
m := app.Party("/middleware")
|
|
|
|
m.Use(j.Verify(func() interface{} {
|
|
|
|
return new(userClaims)
|
|
|
|
}))
|
|
|
|
m.Post("/protected", func(ctx iris.Context) {
|
|
|
|
claims := jwt.Get(ctx)
|
2020-06-07 14:26:06 +02:00
|
|
|
ctx.JSON(claims)
|
|
|
|
})
|
2020-10-17 05:40:17 +02:00
|
|
|
m.Post("/invalidate", func(ctx iris.Context) {
|
|
|
|
ctx.Logout() // OR j.Invalidate(ctx)
|
|
|
|
})
|
2020-06-07 14:26:06 +02:00
|
|
|
|
2020-05-27 11:02:17 +02:00
|
|
|
e := httptest.New(t, app)
|
|
|
|
|
|
|
|
// Get token.
|
|
|
|
rawToken := e.GET("/auth").Expect().Status(httptest.StatusOK).Body().Raw()
|
|
|
|
if rawToken == "" {
|
|
|
|
t.Fatalf("empty token")
|
|
|
|
}
|
|
|
|
|
2020-10-17 05:40:17 +02:00
|
|
|
restrictedPaths := [...]string{"/protected", "/middleware/protected"}
|
2020-05-27 11:02:17 +02:00
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
for _, path := range restrictedPaths {
|
|
|
|
// Authorization Header.
|
|
|
|
e.POST(path).WithHeader("Authorization", "Bearer "+rawToken).Expect().
|
2020-10-17 05:40:17 +02:00
|
|
|
Status(httptest.StatusOK).JSON().Equal(customClaims)
|
2020-05-27 11:02:17 +02:00
|
|
|
|
|
|
|
// URL Query.
|
|
|
|
e.POST(path).WithQuery("token", rawToken).Expect().
|
2020-10-17 05:40:17 +02:00
|
|
|
Status(httptest.StatusOK).JSON().Equal(customClaims)
|
2020-05-27 11:02:17 +02:00
|
|
|
|
|
|
|
// JSON Body.
|
|
|
|
e.POST(path).WithJSON(iris.Map{"access_token": rawToken}).Expect().
|
2020-10-17 05:40:17 +02:00
|
|
|
Status(httptest.StatusOK).JSON().Equal(customClaims)
|
2020-05-27 11:02:17 +02:00
|
|
|
|
|
|
|
// Missing "Bearer".
|
|
|
|
e.POST(path).WithHeader("Authorization", rawToken).Expect().
|
2020-10-17 05:40:17 +02:00
|
|
|
Status(httptest.StatusUnauthorized).Body().Equal("token is missing")
|
2020-05-27 11:02:17 +02:00
|
|
|
}
|
2020-10-17 05:40:17 +02:00
|
|
|
|
|
|
|
// 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")
|
|
|
|
|
2020-05-27 11:02:17 +02:00
|
|
|
expireRemDur := testMaxAge - time.Since(now)
|
|
|
|
|
|
|
|
// Expiration.
|
|
|
|
time.Sleep(expireRemDur /* -end */)
|
|
|
|
for _, path := range restrictedPaths {
|
2020-10-17 05:40:17 +02:00
|
|
|
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.
|
2020-10-17 14:22:42 +02:00
|
|
|
userAPI.Post("/middleware", j.Verify(nil), func(ctx iris.Context) {
|
2020-10-17 05:40:17 +02:00
|
|
|
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")
|
2020-05-27 11:02:17 +02:00
|
|
|
}
|
2020-10-17 05:40:17 +02:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2020-10-17 14:22:42 +02:00
|
|
|
func TestVerifyJSON(t *testing.T) {
|
|
|
|
j := jwt.HMAC(testMaxAge, "secret", "itsa16bytesecret")
|
|
|
|
|
|
|
|
app := iris.New()
|
|
|
|
app.Get("/user/auth", func(ctx iris.Context) {
|
|
|
|
err := j.WriteToken(ctx, iris.Map{"roles": []string{"admin"}})
|
|
|
|
if err != nil {
|
|
|
|
ctx.StopWithError(iris.StatusUnauthorized, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
app.Post("/", j.VerifyJSON(), func(ctx iris.Context) {
|
|
|
|
claims := struct {
|
|
|
|
Roles []string `json:"roles"`
|
|
|
|
}{}
|
|
|
|
jwt.ReadJSON(ctx, &claims)
|
|
|
|
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("/").WithHeader("Authorization", "Bearer "+token).Expect().
|
|
|
|
Status(httptest.StatusOK).JSON().Equal(iris.Map{"roles": []string{"admin"}})
|
|
|
|
|
|
|
|
e.POST("/").Expect().Status(httptest.StatusUnauthorized)
|
|
|
|
}
|
|
|
|
|
2020-10-17 05:40:17 +02:00
|
|
|
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")
|
2020-05-27 11:02:17 +02:00
|
|
|
}
|