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 }