Skip to content

Commit

Permalink
Merge pull request #109 from iegomez/feat/jwt-skip-expiration
Browse files Browse the repository at this point in the history
JWT skip expiration.
  • Loading branch information
iegomez authored Oct 29, 2020
2 parents 1fb8e9a + 0c36103 commit 33ab134
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 23 deletions.
23 changes: 12 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -785,17 +785,18 @@ auth_opt_jwt_remote true

The following `auth_opt_` options are supported by the `jwt` backend when remote is set to true:

| Option | default | Mandatory | Meaning |
| ----------------- | ----------------- | :---------: | ---------- |
| jwt_host | | Y | API server host name or ip |
| jwt_port | | Y | TCP port number |
| jwt_getuser_uri | | Y | URI for check username/password |
| jwt_superuser_uri | | N | URI for check superuser |
| jwt_aclcheck_uri | | Y | URI for check acl |
| jwt_with_tls | false | N | Use TLS on connect |
| jwt_verify_peer | false | N | Whether to verify peer for tls |
| jwt_response_mode | status | N | Response type (status, json, text)|
| jwt_params_mode | json | N | Data type (json, form) |
| Option | default | Mandatory | Meaning |
| --------------------- | ----------------- | :---------: | ---------------------------------- |
| jwt_host | | Y | API server host name or ip |
| jwt_port | | Y | TCP port number |
| jwt_getuser_uri | | Y | URI for check username/password |
| jwt_superuser_uri | | N | URI for check superuser |
| jwt_aclcheck_uri | | Y | URI for check acl |
| jwt_with_tls | false | N | Use TLS on connect |
| jwt_verify_peer | false | N | Whether to verify peer for tls |
| jwt_skip_expiration | false | N | Skip token expiration |
| jwt_response_mode | status | N | Response type (status, json, text) |
| jwt_params_mode | json | N | Data type (json, form) |


URIs (like jwt_getuser_uri) are expected to be in the form `/path`. For example, if jwt_with_tls is `false`, jwt_host is `localhost`, jwt_port `3000` and jwt_getuser_uri is `/user`, mosquitto will send a POST request to `http://localhost:3000/user` to get a response to check against. How data is sent (either json encoded or as form values) and received (as a simple http status code, a json encoded response or plain text), is given by options jwt_response_mode and jwt_params_mode.
Expand Down
32 changes: 22 additions & 10 deletions backends/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,14 @@ type JWT struct {
SuperuserQuery string
AclQuery string

UserUri string
SuperuserUri string
AclUri string
Host string
Port string
WithTLS bool
VerifyPeer bool
UserUri string
SuperuserUri string
AclUri string
Host string
Port string
WithTLS bool
VerifyPeer bool
SkipExpiration bool

ParamsMode string
ResponseMode string
Expand Down Expand Up @@ -87,6 +88,10 @@ func NewJWT(authOpts map[string]string, logLevel log.Level, hasher hashing.HashC
jwt.Remote = true
}

if skipExpiration, ok := authOpts["jwt_skip_expiration"]; ok && skipExpiration == "true" {
jwt.SkipExpiration = true
}

//If remote, set remote api fields. Else, set jwt secret.
if jwt.Remote {

Expand Down Expand Up @@ -473,12 +478,19 @@ func (o JWT) getClaims(tokenStr string) (*Claims, error) {
return []byte(o.Secret), nil
})

expirationError := false
if err != nil {
log.Debugf("jwt parse error: %s", err)
return nil, err
if !o.SkipExpiration {
log.Debugf("jwt parse error: %s", err)
return nil, err
}

if v, ok := err.(*jwt.ValidationError); ok && v.Errors == jwt.ValidationErrorExpired {
expirationError = true
}
}

if !jwtToken.Valid {
if !jwtToken.Valid && !expirationError {
return nil, errors.New("jwt invalid token")
}

Expand Down
84 changes: 82 additions & 2 deletions backends/jwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,94 @@ var wrongJwtToken = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"username": "wrong_user",
})

var expiredToken = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"iss": "jwt-test",
"aud": "jwt-test",
"nbf": nowSecondsSinceEpoch,
"exp": nowSecondsSinceEpoch - int64(time.Hour*24/time.Second),
"sub": "user",
"username": username,
})

func TestJWTClaims(t *testing.T) {
Convey("Correct token should give no errors", t, func() {
// Initialize JWT in local mode.
authOpts := make(map[string]string)
authOpts["jwt_remote"] = "false"
authOpts["jwt_db"] = "postgres"
authOpts["jwt_secret"] = jwtSecret
authOpts["jwt_userquery"] = "select count(*) from test_user where username = $1 limit 1"
authOpts["jwt_superquery"] = "select count(*) from test_user where username = $1 and is_admin = true"
authOpts["jwt_aclquery"] = "SELECT test_acl.topic FROM test_acl, test_user WHERE test_user.username = $1 AND test_acl.test_user_id = test_user.id AND rw >= $2"
authOpts["pg_userquery"] = "mock_string"
authOpts["pg_superquery"] = "mock_string"
authOpts["pg_aclquery"] = "mock_string"
authOpts["jwt_userfield"] = "Username"

//Give necessary postgres options.
authOpts["pg_host"] = "localhost"
authOpts["pg_port"] = "5432"
authOpts["pg_dbname"] = "go_auth_test"
authOpts["pg_user"] = "go_auth_test"
authOpts["pg_password"] = "go_auth_test"

jwt, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""))
So(err, ShouldBeNil)

Convey("Correct token should give no error", func() {
token, err := jwtToken.SignedString([]byte(jwtSecret))
So(err, ShouldBeNil)

_, err = jwt.getClaims(token)
So(err, ShouldBeNil)
})

Convey("A token signed with a different secret should give an error", func() {
token, err := jwtToken.SignedString([]byte("wrong-secret"))
So(err, ShouldBeNil)

_, err = jwt.getClaims(token)
So(err, ShouldNotBeNil)
})

Convey("Wrong user token should give no error", func() {
token, err := wrongJwtToken.SignedString([]byte(jwtSecret))
So(err, ShouldBeNil)

_, err = jwt.getClaims(token)
So(err, ShouldBeNil)
})

Convey("Expired token should give an error when getting claims", func() {
token, err := expiredToken.SignedString([]byte(jwtSecret))
So(err, ShouldBeNil)

_, err = jwt.getClaims(token)
So(err, ShouldNotBeNil)
})

Convey("When setting skip expiration, expired token should not give an error", func() {
authOpts["jwt_skip_expiration"] = "true"

jwt, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""))
So(err, ShouldBeNil)

token, err := expiredToken.SignedString([]byte(jwtSecret))
So(err, ShouldBeNil)

_, err = jwt.getClaims(token)
So(err, ShouldBeNil)
})
})
}

func TestLocalPostgresJWT(t *testing.T) {

Convey("Creating a token should return a nil error", t, func() {
token, err := jwtToken.SignedString([]byte(jwtSecret))
So(err, ShouldBeNil)

//Initialize JWT in local mode.
// Initialize JWT in local mode.
authOpts := make(map[string]string)
authOpts["jwt_remote"] = "false"
authOpts["jwt_db"] = "postgres"
Expand Down Expand Up @@ -187,7 +268,6 @@ func TestLocalPostgresJWT(t *testing.T) {
jwt.Postgres.DB.MustExec("delete from test_acl where 1 = 1")

jwt.Halt()

})

})
Expand Down

0 comments on commit 33ab134

Please sign in to comment.