You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
quartermaster/internal/jwt/jwt.go

106 lines
2.9 KiB

package jwt
import (
"fmt"
"net/http"
"os"
"strings"
"github.com/dustinpianalto/errors"
"github.com/dustinpianalto/quartermaster"
"github.com/golang-jwt/jwt"
)
var secretKey = []byte(os.Getenv("JWT_KEY"))
func AuthenticateJWTToken(req *http.Request) (*quartermaster.Claims, error) {
const method = errors.Method("jwt/AuthenticateJWTToken")
jwtToken, err := extractJWTToken(req)
if err != nil {
return nil, errors.E(method, "could not authenticate token", err)
}
claims, err := ParseJWT(jwtToken, secretKey)
if err != nil {
return nil, errors.E(method, "could not parse token", err)
}
return claims, nil
}
// ExtractJWTToken extracts bearer token from Authorization header
func extractJWTToken(req *http.Request) (string, error) {
const method = errors.Method("jwt/extractJWTToken")
tokenString := req.Header.Get("Authorization")
if tokenString == "" {
return "", errors.E(method, errors.Malformed, "token not found")
}
tokenString, err := stripTokenPrefix(tokenString)
if err != nil {
return "", errors.E(method, "error formatting token", err)
}
return tokenString, nil
}
// Strips 'Token' or 'Bearer' prefix from token string
func stripTokenPrefix(tok string) (string, error) {
// split token to 2 parts
tokenParts := strings.Split(tok, " ")
if len(tokenParts) < 2 {
return tokenParts[0], nil
}
return tokenParts[1], nil
}
func ParseJWT(tokenString string, key []byte) (*quartermaster.Claims, error) {
const method = errors.Method("jwt/ParseJWT")
var claims *quartermaster.Claims = &quartermaster.Claims{}
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
// validate the alg is what is expected:
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.E(errors.Method("jwt/ParseJWT/Parse"), errors.Malformed, fmt.Sprintf("unexpected signing method: %v", token.Header["alg"]))
}
return key, nil
})
if err != nil {
return nil, errors.E(method, "error parsing token", err)
}
if token.Valid {
return claims, nil
} else if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
return nil, errors.E(method, errors.Malformed, "token is malformed")
} else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
// Token is either expired or not active yet
return nil, errors.E(method, errors.Permission, "token is either expired or not yet valid")
} else {
return nil, errors.E(method, errors.Internal, "unknown error with token")
}
} else {
return nil, errors.E(method, errors.Permission, "token is not valid")
}
}
func CreateJWTToken(claims quartermaster.Claims) (string, error) {
const method = errors.Method("jwt/CreateJWTToken")
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString(secretKey)
if err != nil {
return "", errors.E(method, errors.Internal, "error signing token", err)
}
return tokenString, nil
}