Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
legege committed Mar 18, 2023
0 parents commit aca2225
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .traefik.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
displayName: JWT Validation Middleware
type: middleware
import: github.com/legege/jwt-validation-middleware
summary: 'Verify JWT Token in Auth header, Cookie or Query param, and injects decoded payload in header'

testData:
secret: ThisIsMyVerySecret
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2020 23 degrees GmbH

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# JWT Validation Middleware

JWT Validation Middleware is a middleware plugin for [Traefik](https://github.com/containous/traefik) which verifies a JWT token in Auth header, Cookie or Query param, and adds the payload as injected header to the request.

## Configuration

Start with command
```yaml
command:
- "--experimental.plugins.jwt-middleware.modulename=github.com/legege/jwt-middleware"
- "--experimental.plugins.jwt-middleware.version=v0.1.0"
```
Activate plugin in your config
```yaml
http:
middlewares:
my-jwt-middleware:
plugin:
jwt-middleware:
secret: SECRET
payloadHeader: X-Jwt-Payload
authQueryParam: authToken
authCookieName: authToken
```
Use as docker-compose label
```yaml
labels:
- "traefik.http.routers.my-service.middlewares=my-jwt-middleware@file"
```
Forked from https://github.com/23deg/jwt-middleware
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/legege/jwt-validation-middleware

go 1.19
181 changes: 181 additions & 0 deletions jwt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package jwt_middleware

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

"crypto/hmac"
"crypto/sha256"
"encoding/base64"
)

type Config struct {
Secret string `json:"secret,omitempty"`
PayloadHeader string `json:"payloadHeader,omitempty"`
AuthHeader string `json:"authHeader,omitempty"`
AuthHeaderPrefix string `json:"authHeaderPrefix,omitempty"`
AuthQueryParam string `json:"authQueryParam,omitempty"`
AuthCookieName string `json:"authCookieName,omitempty"`
}

func CreateConfig() *Config {
return &Config{}
}

type JWT struct {
next http.Handler
name string
secret string
payloadHeader string
authQueryParam string
authCookieName string
}

func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error) {

if len(config.Secret) == 0 {
config.Secret = "SECRET"
}
if len(config.PayloadHeader) == 0 {
config.PayloadHeader = "X-Jwt-Payload"
}
if len(config.AuthQueryParam) == 0 {
config.AuthQueryParam = "authToken"
}
if len(config.AuthCookieName) == 0 {
config.AuthCookieName = "authToken"
}

return &JWT{
next: next,
name: name,
secret: config.Secret,
payloadHeader: config.PayloadHeader,
authQueryParam: config.AuthQueryParam,
authCookieName: config.AuthCookieName,
}, nil
}

func (j *JWT) ServeHTTP(res http.ResponseWriter, req *http.Request) {
rawToken := j.extractTokenFromHeader(req)
if len(rawToken) == 0 && j.authQueryParam != "" {
rawToken = j.extractTokenFromQuery(req)
}
if len(rawToken) == 0 && j.authCookieName != "" {
rawToken = j.extractTokenFromCookie(req)
}
if len(rawToken) == 0 {
http.Error(res, "Token not provided", http.StatusUnauthorized)
return
}

token, preprocessError := preprocessJWT(rawToken)
if preprocessError != nil {
http.Error(res, preprocessError.Error(), http.StatusBadRequest)
return
}

verified, verificationError := verifyJWT(token, j.secret)
if verificationError != nil {
http.Error(res, verificationError.Error(), http.StatusUnauthorized)
return
}

if verified {
// If true decode payload
payload, decodeErr := decodeBase64(token.payload)
if decodeErr != nil {
http.Error(res, decodeErr.Error(), http.StatusBadRequest)
return
}

// TODO Check for outside of ASCII range characters

// Inject header as proxypayload or configured name
req.Header.Add(j.payloadHeader, payload)
fmt.Println(req.Header)
j.next.ServeHTTP(res, req)
} else {
http.Error(res, "Not allowed", http.StatusUnauthorized)
}
}

func (j *JWT) extractTokenFromCookie(request *http.Request) string {
cookie, err := request.Cookie(j.authCookieName)
if err != nil {
return ""
}
return cookie.Value
}

func (j *JWT) extractTokenFromQuery(request *http.Request) string {
if request.URL.Query().Has(j.authQueryParam) {
return request.URL.Query().Get(j.authQueryParam)
}
return ""
}

func (j *JWT) extractTokenFromHeader(request *http.Request) string {
authHeader, ok := request.Header["Authorization"]
if !ok {
return ""
}
auth := authHeader[0]
if !strings.HasPrefix(auth, "Bearer ") {
return ""
}
return auth[7:]
}

// Token Deconstructed header token
type Token struct {
header string
payload string
verification string
}

// verifyJWT Verifies jwt token with secret
func verifyJWT(token Token, secret string) (bool, error) {
mac := hmac.New(sha256.New, []byte(secret))
message := token.header + "." + token.payload
mac.Write([]byte(message))
expectedMAC := mac.Sum(nil)

decodedVerification, errDecode := base64.RawURLEncoding.DecodeString(token.verification)
if errDecode != nil {
return false, errDecode
}

if hmac.Equal(decodedVerification, expectedMAC) {
return true, nil
}
return false, nil
// TODO Add time check to jwt verification
}

func preprocessJWT(rawToken string) (Token, error) {
var token Token

tokenSplit := strings.Split(rawToken, ".")

if len(tokenSplit) != 3 {
return token, fmt.Errorf("Invalid token")
}

token.header = tokenSplit[0]
token.payload = tokenSplit[1]
token.verification = tokenSplit[2]

return token, nil
}

// decodeBase64 Decode base64 to string
func decodeBase64(baseString string) (string, error) {
byte, decodeErr := base64.RawURLEncoding.DecodeString(baseString)
if decodeErr != nil {
return baseString, fmt.Errorf("Error decoding")
}
return string(byte), nil
}

0 comments on commit aca2225

Please sign in to comment.