Skip to content
Snippets Groups Projects
Commit a2c06864 authored by Jano Hendriks's avatar Jano Hendriks
Browse files

Add JWT functions

parent 76529f01
No related branches found
No related tags found
No related merge requests found
......@@ -20,17 +20,10 @@ func GenerateNewApiKey() string {
return uniqueKey
}
// GetApiKeyFromHeaders checks if a bearer token is passed as part of the Authorization header and returns that key
// GetApiKeyFromHeaders checks if a bearer token is passed as part of the Authorization header and returns that key.
// NOTE: This function is deprecated. It simply calls the more generic GetBearerTokenFromHeaders.
func GetApiKeyFromHeaders(headers map[string]string) string {
key := headers["authorization"]
if key == "" {
key = headers["Authorization"]
}
if strings.HasPrefix(strings.ToLower(key), "bearer ") {
key = strings.TrimPrefix(strings.ToLower(key), "bearer")
return strings.TrimSpace(key)
}
return ""
return GetBearerTokenFromHeaders(headers)
}
// MaskAPIKey masks an API key in the form "abc***xyz"
......
package auth
import (
"golang.org/x/crypto/bcrypt"
"strings"
)
// GetBearerTokenFromHeaders checks if a bearer token is passed as part of the Authorization header and returns that key
func GetBearerTokenFromHeaders(headers map[string]string) string {
headerValue := headers["authorization"]
if headerValue == "" {
headerValue = headers["Authorization"]
}
if strings.HasPrefix(strings.ToLower(headerValue), "bearer ") {
headerValues := strings.Split(headerValue, " ")
return strings.TrimSpace(headerValues[1])
}
return ""
}
// HashPassword returns a hashed version of the provided password.
func HashPassword(password string) (string, error) {
encryptedBytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
if err != nil {
return "", err
}
return string(encryptedBytes), nil
}
// PasswordIsCorrect checks whether the password is correct by validating it against the hashed password.
func PasswordIsCorrect(password string, hashedPassword string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
return err == nil
}
package auth
import (
"encoding/json"
"github.com/golang-jwt/jwt/v4"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/date_utils"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors"
"net/http"
"time"
)
type JsonWebToken struct {
UserID string `json:"user_id"`
Password string `json:"password"`
ProviderID int64 `json:"provider_id,omitempty"`
ExpiryDate time.Time `json:"expiry_date"`
}
// GenerateJWT takes the payload and generates a signed JWT using the provided secret
func GenerateJWT(payload JsonWebToken, secret []byte) (string, error) {
// Convert the JsonWebToken to a map[string]interface{}
tokenBytes, err := json.Marshal(payload)
if err != nil {
return "", err
}
tokenMap := make(map[string]interface{})
err = json.Unmarshal(tokenBytes, &tokenMap)
if err != nil {
return "", err
}
// Create the signed token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims(tokenMap))
tokenString, err := token.SignedString(secret)
if err != nil {
return "", err
}
return tokenString, nil
}
// ValidateJWT parses the JWT and validates that it is signed correctly
func ValidateJWT(tokenString string, secret []byte) (JsonWebToken, error) {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// Validate the signing method
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return secret, nil
})
if err != nil {
return JsonWebToken{}, err
}
if token == nil {
return JsonWebToken{}, errors.Error("could not get token from token string")
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok || !token.Valid {
return JsonWebToken{}, errors.Error("invalid token")
}
// Convert the MapClaims to a JsonWebToken
claimsBytes, err := json.Marshal(claims)
if err != nil {
return JsonWebToken{}, err
}
var jsonWebToken JsonWebToken
err = json.Unmarshal(claimsBytes, &jsonWebToken)
if err != nil {
return JsonWebToken{}, err
}
// Validate the expiry date
if jsonWebToken.ExpiryDate.Before(date_utils.CurrentDate()) {
return jsonWebToken, errors.Error("token has expired")
}
return jsonWebToken, nil
}
// LoginWithPassword checks that the provided password is correct. If the password is correct, a signed JWT is returned
// using the provided encryption key.
func LoginWithPassword(password string, hashedPassword string, jsonWebToken JsonWebToken, jwtEncryptionKey string) (string, error) {
if PasswordIsCorrect(password, hashedPassword) {
return GenerateJWT(jsonWebToken, []byte(jwtEncryptionKey))
}
return "", errors.HTTPWithMsg(http.StatusBadRequest, "password is incorrect")
}
package auth
import (
"fmt"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/date_utils"
"testing"
"time"
)
func TestJWT(t *testing.T) {
secret := []byte("THISISTHESECRETKEY")
jwToken := JsonWebToken{
UserID: "janoadmin",
Password: "Password123",
ProviderID: 1,
ExpiryDate: date_utils.CurrentDate().Add(time.Second * 1),
}
// Validate before expire
fmt.Println("TEST VALID JWT")
jwTokenString, err := GenerateJWT(jwToken, secret)
if err != nil {
fmt.Println("!!!!FAIL", err.Error())
t.FailNow()
}
validatedToken, err := ValidateJWT(jwTokenString, secret)
if err != nil {
fmt.Println("!!!!FAIL", err.Error())
t.Fail()
} else {
fmt.Println(" SUCCESS", validatedToken)
}
// Validate invalid signature
fmt.Println("TEST INVALID JWT SIGNATURE")
jwTokenString, err = GenerateJWT(jwToken, []byte("INCORRECTSECRETVALUE"))
if err != nil {
fmt.Println("!!!!FAIL", err.Error())
t.FailNow()
}
validatedToken, err = ValidateJWT(jwTokenString, secret)
if err == nil {
fmt.Println("!!!!FAIL")
t.Fail()
} else {
fmt.Println(" SUCCESS", err.Error())
}
time.Sleep(time.Second * 1)
// Validate after expire
fmt.Println("TEST EXPIRED JWT")
jwTokenString, err = GenerateJWT(jwToken, secret)
if err != nil {
fmt.Println("!!!!FAIL", err.Error())
t.FailNow()
}
validatedToken, err = ValidateJWT(jwTokenString, secret)
if err == nil {
fmt.Println("!!!!FAIL")
t.Fail()
} else {
fmt.Println(" SUCCESS", err.Error())
}
fmt.Println("Done!")
}
......@@ -27,6 +27,7 @@ require (
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/go-errors/errors v1.4.1 // indirect
github.com/go-pg/zerochecker v0.2.0 // indirect
github.com/golang-jwt/jwt/v4 v4.4.3 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment