Web token authentication in Golang I am new to Go and the web token / JTW concept. In order to learn Go and learn about web tokens, I am making my own implementation in Go (never to be used, of course).

If a user gets authenticated (assume that part is secure), I generate a web token, [header].[payload].[signature]. The header contains a timestamp of when the token was created. The payload has the user id and maybe role in there too, and the signature is there to show that the payload hasn't been altered.


Are there any security concerns with this?
Is there a way to improve the performance / speed of this?

package gwt

import (
    "crypto/hmac"
    "crypto/sha512"
    "crypto/subtle"
    "encoding/base64"
    "encoding/json"
    "errors"
    "fmt"
    "os"
    "strings"
    "time"
)

var Expired error = errors.New("Token expired")
var Invalid error = errors.New("Invalid token")

var expireAfter int = 60 * 60 * 24 * 7
var refreshAfter int = 60 * 60 * 24 * 3
var secret []byte

func init() {
    gwtSecret := os.Getenv("GWT_SECRET")
    if gwtSecret == "" {
        fmt.Println("You must set GWT_SECRET environment variable: export GWT_SECRET=change_this")
        os.Exit(0)
    } else if len(gwtSecret) &lt; 16 {
        fmt.Println("GWT_SECRET must be longer than 16 characters")
        os.Exit(0)
    }

    secret = []byte(gwtSecret)
}

func Expire(e int, r int) {
    expireAfter = e
    refreshAfter = r
}

func Make(object map[string]interface{}) (string, error) {
    exp := time.Now().Format(time.UnixDate)
    header := base64.StdEncoding.EncodeToString([]byte(exp))

    payloadBytes, err := json.Marshal(object)
    if err != nil {
        return "", err
    }

    payload := base64.StdEncoding.EncodeToString(payloadBytes)

    return header + "." + payload + "." + sign(header+payload), nil
}

func GetPayload(token string) (map[string]interface{}, string, error) {
    parts := strings.Split(token, ".")
    if len(parts) != 3 || subtle.ConstantTimeCompare([]byte(sign(parts[0]+parts[1])), []byte(parts[2])) != 1 {
        return nil, "", Invalid
    }

    headerBytes, err := base64.StdEncoding.DecodeString(parts[0])
    if err != nil {
        return nil, "", Invalid
    }

    header := string(headerBytes)

    created, err := time.Parse(time.UnixDate, header)
    if err != nil {
        return nil, "", Invalid
    }

    expired := created.Add(time.Second * time.Duration(expireAfter)).Before(time.Now())
    if expired {
        return nil, "", Expired
    }

    payloadBytes, err := base64.StdEncoding.DecodeString(parts[1])
    if err != nil {
        return nil, "", Invalid
    }

    var payload map[string]interface{}
    if err := json.Unmarshal(payloadBytes, &amp;payload); err != nil {
        return nil, "", Invalid
    }

    var newToken string
    stale := created.Add(time.Second * time.Duration(refreshAfter)).Before(time.Now())
    if stale {
        newToken, err = Make(payload)
        if err != nil {
            return nil, "", Invalid
        }
    } else {
        newToken = ""
    }

    return payload, newToken, nil
}

func sign(s string) string {
    h := hmac.New(sha512.New, secret)
    h.Write([]byte(s))

    return base64.StdEncoding.EncodeToString(h.Sum(nil))
}



The token will be stored locally on the client, sent via http headers on the necessary requests, and everything is https.

An updated version can be found here.
Go Code Review Comments
  
  Don't Panic
  
  Don't use panic for normal error handling. Use error and multiple
  return values.


func Make(object interface{}) string {
    payloadBytes, err := json.Marshal(object)
    if err != nil {
        panic(err)
    }
    payload := base64.StdEncoding.EncodeToString(payloadBytes)
    return payload + "." + sign(payload)
}


I'm going to exploit this, an exported function that panics, to crash your application.

func sign(s string) string {
    h := hmac.New(sha256.New, []byte("secret"))
    h.Write([]byte(s))
    return base64.StdEncoding.EncodeToString(h.Sum(nil))
}


I'm going use a literal embedded in your executable, []byte("secret"), to break your authentication.