Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(authN): Redesign JWT token auth #372 #394

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ LOCAL_TEST_DB=true
SEED_MODE=false
```

To enable JWT token authentication, define `AUTH_TOKEN_SECRET` environment variable. Those variable is read by application on startup to start token validation middleware.

### Docker

The `docker-compose.yml` file defines two profiles: `db` for the `heureka-db` service and `heureka` for the `heureka-app` service.
Expand Down
2 changes: 0 additions & 2 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ services:
DB_NAME: ${DB_NAME}
DB_SCHEMA: /app_sqlschema/schema.sql
SEED_MODE: ${SEED_MODE}
AUTH_TYPE: token
AUTH_TOKEN_SECRET: xxx
volumes:
- ./internal/database/mariadb/init/schema.sql:/app_sqlschema/schema.sql
depends_on:
Expand Down
57 changes: 44 additions & 13 deletions internal/api/graphql/access/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
package access

import (
"strings"
"fmt"
"net/http"
"reflect"

"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
Expand All @@ -17,23 +19,52 @@ type Logger interface {
Warn(...interface{})
}

type Auth interface {
GetMiddleware() gin.HandlerFunc
func NewAuth(cfg *util.Config) *Auth {
l := newLogger()
auth := Auth{logger: l}
auth.AppendInstance(NewTokenAuthMethod(l, cfg))
//TODO: auth.AppendInstance(NewOidcAuthMethod(l, cfg))
return &auth
}

func NewAuth(cfg *util.Config) Auth {
l := newLogger()
type Auth struct {
chain []AuthMethod
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm missing the full picture, but why exactly do we need a chain of auth methods?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to have the same API access via JWT token (scanner) as well as for OIDC (user)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm.. Even if OIDC uses JWTs as part of its specification, we do need to treat both auth mechanisms differently. I mean, you also have different fields (inside the JWT token payload, see this example). Or how is this supposed to work? Maybe we need some call to clarify this.

logger Logger
}

authType := strings.ToLower(cfg.AuthType)
if authType == "token" {
return NewTokenAuth(l, cfg)
} else if authType == "none" {
return NewNoAuth()
}
type AuthMethod interface {
Verify(*gin.Context) error
}

l.Warn("AUTH_TYPE is not set, assuming 'none' authorization method")
func (a *Auth) GetMiddleware() gin.HandlerFunc {
return func(authCtx *gin.Context) {
if len(a.chain) > 0 {
var retMsg string
for _, auth := range a.chain {
if err := auth.Verify(authCtx); err == nil {
authCtx.Next()
return
} else {
if retMsg != "" {
retMsg = fmt.Sprintf("%s, ", retMsg)
}
retMsg = fmt.Sprintf("%s%s", retMsg, err)
}
}
a.logger.Error("Unauthorized access: %s", retMsg)
authCtx.JSON(http.StatusUnauthorized, gin.H{"error": retMsg})
authCtx.Abort()
return
}
authCtx.Next()
return
}
}

return NewNoAuth()
func (a *Auth) AppendInstance(am AuthMethod) {
if !reflect.ValueOf(am).IsNil() {
a.chain = append(a.chain, am)
}
}

func newLogger() Logger {
Expand Down
21 changes: 0 additions & 21 deletions internal/api/graphql/access/no_auth.go

This file was deleted.

20 changes: 10 additions & 10 deletions internal/api/graphql/access/test/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
)

const (
testUsername = "testUser"
testClientName = "testClientName"
)

func SendGetRequest(url string, headers map[string]string) *http.Response {
Expand Down Expand Up @@ -53,7 +53,7 @@ type Jwt struct {
signingMethod jwt.SigningMethod
signKey interface{}
expiresAt *jwt.NumericDate
username string
name string
}

func NewJwt(secret string) *Jwt {
Expand All @@ -64,8 +64,8 @@ func NewRsaJwt(privKey *rsa.PrivateKey) *Jwt {
return &Jwt{signKey: privKey, signingMethod: jwt.SigningMethodRS256}
}

func (j *Jwt) WithUsername(username string) *Jwt {
j.username = username
func (j *Jwt) WithName(name string) *Jwt {
j.name = name
return j
}

Expand All @@ -81,7 +81,7 @@ func (j *Jwt) String() string {
ExpiresAt: j.expiresAt,
IssuedAt: jwt.NewNumericDate(time.Now()),
Issuer: "heureka",
dorneanu marked this conversation as resolved.
Show resolved Hide resolved
Subject: j.username,
Subject: j.name,
},
}
token := jwt.NewWithClaims(j.signingMethod, claims)
Expand All @@ -92,15 +92,15 @@ func (j *Jwt) String() string {
}

func GenerateJwt(jwtSecret string, expiresIn time.Duration) string {
return NewJwt(jwtSecret).WithExpiresAt(time.Now().Add(expiresIn)).WithUsername(testUsername).String()
return NewJwt(jwtSecret).WithExpiresAt(time.Now().Add(expiresIn)).WithName(testClientName).String()
}

func GenerateJwtWithUsername(jwtSecret string, expiresIn time.Duration, username string) string {
return NewJwt(jwtSecret).WithExpiresAt(time.Now().Add(expiresIn)).WithUsername(username).String()
func GenerateJwtWithName(jwtSecret string, expiresIn time.Duration, name string) string {
return NewJwt(jwtSecret).WithExpiresAt(time.Now().Add(expiresIn)).WithName(name).String()
}

func GenerateInvalidJwt(jwtSecret string) string {
return NewJwt(jwtSecret).WithUsername(testUsername).String()
return NewJwt(jwtSecret).WithName(testClientName).String()
}

func GenerateRsaPrivateKey() *rsa.PrivateKey {
Expand All @@ -110,5 +110,5 @@ func GenerateRsaPrivateKey() *rsa.PrivateKey {
}

func GenerateJwtWithInvalidSigningMethod(jwtSecret string, expiresIn time.Duration) string {
return NewRsaJwt(GenerateRsaPrivateKey()).WithExpiresAt(time.Now().Add(expiresIn)).WithUsername(testUsername).String()
return NewRsaJwt(GenerateRsaPrivateKey()).WithExpiresAt(time.Now().Add(expiresIn)).WithName(testClientName).String()
}
116 changes: 0 additions & 116 deletions internal/api/graphql/access/token_auth.go

This file was deleted.

Loading
Loading