From 9c51b91dd7353d6f2aa9980d01444b37180e047b Mon Sep 17 00:00:00 2001 From: Daniel Higuero Date: Fri, 5 May 2023 22:45:56 +0200 Subject: [PATCH] PG-1247 add methods to parse unverified tokens --- README.md | 12 +++++++++--- pkg/config/jwt_config.go | 14 +++++++------- pkg/njwt/claims.go | 8 ++++++++ pkg/njwt/doc.go | 20 ++++++++++++++++++++ pkg/njwt/token.go | 36 ++++++++++++++++++++++++++++++++---- pkg/njwt/token_test.go | 22 ++++++++++++++++++++++ 6 files changed, 98 insertions(+), 14 deletions(-) create mode 100644 pkg/njwt/doc.go diff --git a/README.md b/README.md index 8459693..a106f0d 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,13 @@ # NJWT + JWT library for the Napptive projects The purpose of this project is to provide a simple JWT library to handle the authentication tokens of the Napptive platform. ## Usage +To create new tokens: + ```go pc := NewAuthxClaim("userID", "username") claim := NewClaim("tt", time.Hour, pc) @@ -15,9 +18,12 @@ The purpose of this project is to provide a simple JWT library to handle the aut recoveredClaim, err := tokenMgr.Recover(*token, secret, &AuthxClaim{}) recoveredPC, ok := recClaim.PersonalClaim.(*AuthxClaim) ``` + #### JWT Interceptor -this interceptor validates a JWT token received -``` + +To create an interceptor that validates incoming gRPC calls with a JWT on an authorization header in the context: + +```go cfg := config.JWTConfig{ Secret: "mysecret", Header: "authorization", @@ -33,7 +39,7 @@ s = grpc.NewServer(interceptor.WithServerJWTInterceptor(config)) ## License - Copyright 2020 Napptive + Copyright 2023 Napptive Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/config/jwt_config.go b/pkg/config/jwt_config.go index 66cf88d..7ec9cae 100644 --- a/pkg/config/jwt_config.go +++ b/pkg/config/jwt_config.go @@ -1,6 +1,5 @@ - /** - * Copyright 2020 Napptive + * Copyright 2023 Napptive * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,12 +17,13 @@ package config import ( + "strings" + "github.com/napptive/nerrors/pkg/nerrors" "github.com/rs/zerolog/log" - "strings" ) -// JWT with the JWT configuration +// JWTConfig with the JWT configuration type JWTConfig struct { // Secret with the token secret, used to recover the token Secret string @@ -32,7 +32,7 @@ type JWTConfig struct { } // NewJWTConfig creates a config object with a given secret and header. -func NewJWTConfig (secret string, header string) JWTConfig { +func NewJWTConfig(secret string, header string) JWTConfig { return JWTConfig{ Secret: secret, Header: header, @@ -40,7 +40,7 @@ func NewJWTConfig (secret string, header string) JWTConfig { } // IsValid checks if the configuration options are valid. -func (jc JWTConfig) IsValid () error { +func (jc JWTConfig) IsValid() error { if jc.Secret == "" { return nerrors.NewInvalidArgumentError("secret must be filled") } @@ -51,7 +51,7 @@ func (jc JWTConfig) IsValid () error { } // Print the configuration using the application logger. -func (jc JWTConfig) Print () { +func (jc JWTConfig) Print() { log.Info().Str("header", jc.Header). Str("secret", strings.Repeat("*", len(jc.Secret))).Msg("Authorization") } diff --git a/pkg/njwt/claims.go b/pkg/njwt/claims.go index 28b517b..c2cb367 100644 --- a/pkg/njwt/claims.go +++ b/pkg/njwt/claims.go @@ -114,6 +114,14 @@ type ExtendedAuthxClaim struct { AuthxClaim } +// ToClaim transforms an ExtendedAuthxClaim into a standard Claim. +func (eac *ExtendedAuthxClaim) ToClaim() *Claim { + return &Claim{ + StandardClaims: eac.StandardClaims, + PersonalClaim: eac.AuthxClaim, + } +} + // RefreshClaim is the information stored to create the refresh token. type RefreshClaim struct { UserID string diff --git a/pkg/njwt/doc.go b/pkg/njwt/doc.go new file mode 100644 index 0000000..3adc7ea --- /dev/null +++ b/pkg/njwt/doc.go @@ -0,0 +1,20 @@ +/** + * Copyright 2023 Napptive + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Package njwt provides access to a set of structures and methods related to the creation, validation, and +// inspection of JWT. +package njwt + diff --git a/pkg/njwt/token.go b/pkg/njwt/token.go index db7c6e3..9510eae 100644 --- a/pkg/njwt/token.go +++ b/pkg/njwt/token.go @@ -1,5 +1,5 @@ /** - * Copyright 2020 Napptive + * Copyright 2023 Napptive * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,11 +18,13 @@ package njwt import ( "fmt" + "github.com/golang-jwt/jwt" "github.com/rs/xid" "github.com/rs/zerolog/log" ) +// generateUUID generates a new UUID. func generateUUID() string { return xid.New().String() } @@ -31,9 +33,18 @@ func generateUUID() string { type TokenManager interface { // Generate a new token with a claim. Generate(claim *Claim, secret string) (*string, error) - // Recover the claim from a token, if you want to recover the personal claim yo must include the appropiated object. - // Example: recoveredClaim, err := tokenMgr.Recover(*token, secret, &AuthxClaim{}) + // Recover the claim from a token, if you want to recover the personal claim you must include the appropriate object. + // Example: + // recoveredClaim, err := tokenMgr.Recover(*token, secret, &AuthxClaim{}) Recover(tk string, secret string, pc interface{}) (*Claim, error) + // RecoverUnverified parses the token returning the parsed claim. + // NOTICE: This method does not verify the authenticity of the token as no secret is used to check it. + // This method is intended to be used by clients that need to check data from a token provided by the library + // without the capability of checking its validity. + // Example: + // personalClaim := &AuthxClaim{} + // unverifiedClaim, err := tokenMgr.RecoverUnverified(*token, personalClaim) + RecoverUnverified(tk string, pc interface{}) (*Claim, error) } // New create a new instance of the token generator. @@ -54,7 +65,8 @@ func (*manager) Generate(claim *Claim, secret string) (*string, error) { } // Recover the claim from a token, if you want to recover the personal claim yo must include the appropiated object. -// Example: recoveredClaim, err := tokenMgr.Recover(*token, secret, &AuthxClaim{}) +// Example: +// recoveredClaim, err := tokenMgr.Recover(*token, secret, &AuthxClaim{}) func (*manager) Recover(tk string, secret string, pc interface{}) (*Claim, error) { claim := &Claim{PersonalClaim: pc} _, err := jwt.ParseWithClaims(tk, claim, func(token *jwt.Token) (interface{}, error) { @@ -73,3 +85,19 @@ func (*manager) Recover(tk string, secret string, pc interface{}) (*Claim, error } return claim, nil } + +// RecoverUnverified parses the token returning the parsed claim. +// NOTICE: This method does not verify the authenticity of the token as no secret is used to check it. +// This method is intended to be used by clients that need to check data from a token provided by the library +// without the capability of checking its validity. +// Example: +// personalClaim := &AuthxClaim{} +// unverifiedClaim, err := tokenMgr.RecoverUnverified(*token, personalClaim) +func (*manager) RecoverUnverified(tk string, pc interface{}) (*Claim, error) { + claim := &Claim{PersonalClaim: pc} + _, _, err := new(jwt.Parser).ParseUnverified(tk, claim) + if err != nil { + return nil, err + } + return claim, nil +} diff --git a/pkg/njwt/token_test.go b/pkg/njwt/token_test.go index 1eba5f2..8740668 100644 --- a/pkg/njwt/token_test.go +++ b/pkg/njwt/token_test.go @@ -91,4 +91,26 @@ var _ = ginkgo.Describe("NJWT Token Manager tests", func() { }) }) + ginkgo.Context("working with unverified tokens", func() { + tokenMgr := New() + ginkgo.It("should be able to retrieve raw information from a token", func() { + pc := NewAuthxClaim("userID", "username", utils.GetTestAccountId(), utils.GetTestUserName(), + utils.GetTestEnvironmentId(), true, "zoneID", "zoneURL") + claim := NewClaim("tt", time.Hour, pc) + + secret := "secret" + token, err := tokenMgr.Generate(claim, secret) + gomega.Expect(err).To(gomega.Succeed()) + gomega.Expect(token).NotTo(gomega.BeNil()) + + personalClaim := &AuthxClaim{} + unverifiedClaim, err := tokenMgr.RecoverUnverified(*token, personalClaim) + gomega.Expect(err).To(gomega.Succeed()) + gomega.Expect(unverifiedClaim).ShouldNot(gomega.BeNil()) + unverifiedAuthClaim := unverifiedClaim.GetAuthxClaim() + gomega.Expect(unverifiedAuthClaim).ShouldNot(gomega.BeNil()) + gomega.Expect(unverifiedAuthClaim).Should(gomega.BeEquivalentTo(pc)) + }) + + }) })