I'm trying to write simple JWT implementation with these functionalities:
Generating token using HMAC
Validating token (if signature is correct or exp is not timed out)
Decode token and getting claims
from scratch for better understanding how does it work in depth.
So far I found this article how to build an authentication microservice in golang from scratch. One chapter is dedicated to implementation JWT from scratch. I used it go generate token, however when I paste token in https://jwt.io I've got invalid signature and following warnings:
Warning: Looks like your JWT signature is not encoded correctly using base64url (https://tools.ietforg/html/rfc4648#section-5). Note that padding ("=") must be omitted as per https://tools.ietf.org/html/rfc7515#section-2
Warning: Looks like your JWT header is not encoded correctly using base64url (https://tools.ietforg/html/rfc4648#section-5). Note that padding ("=") must be omitted as per https://tools.ietf.org/html/rfc7515#section-2
Warning: Looks like your JWT payload is not encoded correctly using base64url (https://tools.ietforg/html/rfc4648#section-5). Note that padding ("=") must be omitted as per https://tools.ietf.org/html/rfc7515#section-2
Token I paste look like below:
eyAiYWxnIjogIkhTMjU2IiwgInR5cCI6ICJKV1QiIH0=.eyJhdWQiOiJmcm9udGVuZC5rbm93c2VhcmNoLm1sIiwiZXhwIjoiMTY1MTIyMjcyMyIsImlzcyI6Imtub3dzZWFyY2gubWwifQ==.SqCW8Hxakzck9Puzl0BEOkREPDyl38g2Fd4KFaDazV4=
My JWT code implementation:
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"strings"
"time"
)
func GenerateToken(header string, payload map[string]string, secret string) (string, error) {
h := hmac.New(sha256.New, []byte(secret))
header64 := base64.StdEncoding.EncodeToString([]byte(header))
payloadstr, err := json.Marshal(payload)
if err != nil {
return "", err
}
payload64 := base64.StdEncoding.EncodeToString(payloadstr)
message := header64 + "." + payload64
unsignedStr := header + string(payloadstr)
h.Write([]byte(unsignedStr))
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
tokenStr := message + "." + signature
return tokenStr, nil
}
func ValidateToken(token string, secret string) (bool, error) {
splitToken := strings.Split(token, ".")
if len(splitToken) != 3 {
return false, nil
}
header, err := base64.StdEncoding.DecodeString(splitToken[0])
if err != nil {
return false, err
}
payload, err := base64.StdEncoding.DecodeString(splitToken[1])
if err != nil {
return false, err
}
unsignedStr := string(header) + string(payload)
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(unsignedStr))
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
fmt.Println(signature)
if signature != splitToken[2] {
return false, nil
}
return true, nil
}
func main() {
claimsMap := map[string]string{
"aud": "frontend.knowsearch.ml",
"iss": "knowsearch.ml",
"exp": fmt.Sprint(time.Now().Add(time.Second * 2).Unix()),
}
secret := "Secure_Random_String"
header := `{ "alg": "HS256", "typ": "JWT" }`
tokenString, err := GenerateToken(header, claimsMap, secret)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("token: ", tokenString)
isValid, _ := ValidateToken(tokenString, secret)
fmt.Println("is token valid: ", isValid)
duration := time.Second * 4
time.Sleep(duration)
isValid, _ = ValidateToken(tokenString, secret)
fmt.Println("is token valid: ", isValid)
}
What's wrong with implementation above and how to fix it and get rid of warnings?
I decided to use Golang for implementation, however examples in any other languages very appreciated.
JWT specification requires that all padding = characters are removed:
Base64 encoding using the URL- and filename-safe character set
defined in Section 5 of RFC 4648 [RFC4648], with all trailing '='
characters omitted (as permitted by Section 3.2) and without the
inclusion of any line breaks, whitespace, or other additional
characters.
You can use base64.RawURLEncoding , which creates Base64Url encoding without padding, instead of base64.StdEncoding.
You can see the differences between the StdEncoding, RawStdEncodingand RawURLEncoding in this short Go Playground example.
Also, I strongly recommend to use a JWT library if it's not for learning exercise.
Related
According to Coinbase pro API docs:
The CB-ACCESS-SIGN header is generated by creating a sha256 HMAC using the base64-decoded secret key on the prehash string timestamp +
method + requestPath + body (where + represents string concatenation)
and base64-encode the output. The timestamp value is the same as the
CB-ACCESS-TIMESTAMP header.
The body is the request body string or omitted if there is no request
body (typically for GET requests).
The method should be UPPER CASE.
I borrowed a signing function from a better programmer and feed it something like this:
1619383731POST/reports{{"end_date":"2021-01-02T11:59:59Z","start_date":"2020-01-01T00:00:00Z","type":"account"}}
But keep getting invalid signature from Coinbase.
Signing function for reference:
// sign
func (e *exchange) sign(msg string) string {
key, err := base64.StdEncoding.DecodeString(e.http.secret)
if e.checkErr(err) {
return "bad_sig"
}
signature := hmac.New(sha256.New, key)
_, err = signature.Write([]byte(msg))
if e.checkErr(err) {
return "bad_sig"
}
return base64.StdEncoding.EncodeToString(signature.Sum(nil))
}
Where am I screwing up?
Have you checked out the go-coinbase github repo implementing this as:
https://github.com/preichenberger/go-coinbasepro/blob/master/client.go
h := make(map[string]string)
h["CB-ACCESS-KEY"] = c.Key
h["CB-ACCESS-PASSPHRASE"] = c.Passphrase
h["CB-ACCESS-TIMESTAMP"] = timestamp
message := fmt.Sprintf(
"%s%s%s%s",
timestamp,
method,
url,
data,
)
sig, err := generateSig(message, c.Secret)
if err != nil {
return nil, err
}
h["CB-ACCESS-SIGN"] = sig
return h, nil
It turns out using req.Body directly is the culprit. Why I don't know and something I need to find out, but reading into a []byte and then casting to a string solves part of it.
I'm using JWKS format to provide from an authentication service the public key that can be used to validate tokens coming from that authentication service. However, to perform validation I need to rebuild the public key from the JWK. How can I convert it?
type JWKeys struct {
Keys []JWKey `json:"keys"`
}
type JWKey struct {
Kty string `json:"kty"`
Use string `json:"use,omitempty"`
Kid string `json:"kid,omitempty"`
Alg string `json:"alg,omitempty"`
Crv string `json:"crv,omitempty"`
X string `json:"x,omitempty"`
Y string `json:"y,omitempty"`
D string `json:"d,omitempty"`
N string `json:"n,omitempty"`
E string `json:"e,omitempty"`
K string `json:"k,omitempty"`
}
var PublicKey *rsa.PublicKey
func SetUpExternalAuth() {
res, err := http.Get("my_url")
if err != nil {
log.Fatal("Can't retrieve the key for authentication")
}
bodyBytes, err := ioutil.ReadAll(res.Body)
if err != nil {
log.Fatal(err)
}
var keys JWKeys
json.Unmarshal(bodyBytes, &keys)
//CONVERT JWK TO *rsa.PUBLICKEY???
}
UPDATE
I tried to parse the JWKs using github.com/lestrrat-go/jwx/jwk library, however I couldn't find how to continue:
set,err := jwk.Parse(bodyBytes)
key,err2 := set.Get(0)
//HOW TO CONVERT KEY INTO A *rsa.PublicKey?
At the end I've manually converted it:
if singleJWK.Kty != "RSA" {
log.Fatal("invalid key type:", singleJWK.Kty)
}
// decode the base64 bytes for n
nb, err := base64.RawURLEncoding.DecodeString(singleJWK.N)
if err != nil {
log.Fatal(err)
}
e := 0
// The default exponent is usually 65537, so just compare the
// base64 for [1,0,1] or [0,1,0,1]
if singleJWK.E == "AQAB" || singleJWK.E == "AAEAAQ" {
e = 65537
} else {
// need to decode "e" as a big-endian int
log.Fatal("need to deocde e:", singleJWK.E)
}
PublicKey = &rsa.PublicKey{
N: new(big.Int).SetBytes(nb),
E: e,
}
Understand you have a solution but as you were making the attempt using github.com/lestrrat-go/jwx/jwk here is an approach with that package (pretty much what is in the example):
package main
import (
"context"
"crypto/rsa"
"fmt"
"log"
"github.com/lestrrat-go/jwx/jwk"
)
func main() {
// Example jwk from https://www.googleapis.com/oauth2/v3/certs (but with only one cert for simplicity)
jwkJSON := `{
"keys": [
{
"kty": "RSA",
"n": "o76AudS2rsCvlz_3D47sFkpuz3NJxgLbXr1cHdmbo9xOMttPMJI97f0rHiSl9stltMi87KIOEEVQWUgMLaWQNaIZThgI1seWDAGRw59AO5sctgM1wPVZYt40fj2Qw4KT7m4RLMsZV1M5NYyXSd1lAAywM4FT25N0RLhkm3u8Hehw2Szj_2lm-rmcbDXzvjeXkodOUszFiOqzqBIS0Bv3c2zj2sytnozaG7aXa14OiUMSwJb4gmBC7I0BjPv5T85CH88VOcFDV51sO9zPJaBQnNBRUWNLh1vQUbkmspIANTzj2sN62cTSoxRhSdnjZQ9E_jraKYEW5oizE9Dtow4EvQ",
"use": "sig",
"alg": "RS256",
"e": "AQAB",
"kid": "6a8ba5652a7044121d4fedac8f14d14c54e4895b"
}
]
}
`
set, err := jwk.Parse([]byte(jwkJSON))
if err != nil {
panic(err)
}
fmt.Println(set)
for it := set.Iterate(context.Background()); it.Next(context.Background()); {
pair := it.Pair()
key := pair.Value.(jwk.Key)
var rawkey interface{} // This is the raw key, like *rsa.PrivateKey or *ecdsa.PrivateKey
if err := key.Raw(&rawkey); err != nil {
log.Printf("failed to create public key: %s", err)
return
}
// We know this is an RSA Key so...
rsa, ok := rawkey.(*rsa.PublicKey)
if !ok {
panic(fmt.Sprintf("expected ras key, got %T", rawkey))
}
// As this is a demo just dump the key to the console
fmt.Println(rsa)
}
}
I wrote a Go package exactly for this purpose: github.com/MicahParks/keyfunc
Converting to a *rsa.PublicKey
In this pacakge a JSON Web Key (JWK) looks like this Go struct. It supports both ECDSA and RSA JWK.
// JSONKey represents a raw key inside a JWKS.
type JSONKey struct {
Curve string `json:"crv"`
Exponent string `json:"e"`
ID string `json:"kid"`
Modulus string `json:"n"`
X string `json:"x"`
Y string `json:"y"`
precomputed interface{}
}
After the raw JSON message is unmarshaled into the above struct, this method converts it to an *rsa.PublicKey.
package keyfunc
import (
"crypto/rsa"
"encoding/base64"
"fmt"
"math/big"
)
const (
// rs256 represents a public cryptography key generated by a 256 bit RSA algorithm.
rs256 = "RS256"
// rs384 represents a public cryptography key generated by a 384 bit RSA algorithm.
rs384 = "RS384"
// rs512 represents a public cryptography key generated by a 512 bit RSA algorithm.
rs512 = "RS512"
// ps256 represents a public cryptography key generated by a 256 bit RSA algorithm.
ps256 = "PS256"
// ps384 represents a public cryptography key generated by a 384 bit RSA algorithm.
ps384 = "PS384"
// ps512 represents a public cryptography key generated by a 512 bit RSA algorithm.
ps512 = "PS512"
)
// RSA parses a JSONKey and turns it into an RSA public key.
func (j *JSONKey) RSA() (publicKey *rsa.PublicKey, err error) {
// Check if the key has already been computed.
if j.precomputed != nil {
return j.precomputed.(*rsa.PublicKey), nil
}
// Confirm everything needed is present.
if j.Exponent == "" || j.Modulus == "" {
return nil, fmt.Errorf("%w: rsa", ErrMissingAssets)
}
// Decode the exponent from Base64.
//
// According to RFC 7518, this is a Base64 URL unsigned integer.
// https://tools.ietf.org/html/rfc7518#section-6.3
var exponent []byte
if exponent, err = base64.RawURLEncoding.DecodeString(j.Exponent); err != nil {
return nil, err
}
// Decode the modulus from Base64.
var modulus []byte
if modulus, err = base64.RawURLEncoding.DecodeString(j.Modulus); err != nil {
return nil, err
}
// Create the RSA public key.
publicKey = &rsa.PublicKey{}
// Turn the exponent into an integer.
//
// According to RFC 7517, these numbers are in big-endian format.
// https://tools.ietf.org/html/rfc7517#appendix-A.1
publicKey.E = int(big.NewInt(0).SetBytes(exponent).Uint64())
// Turn the modulus into a *big.Int.
publicKey.N = big.NewInt(0).SetBytes(modulus)
// Keep the public key so it won't have to be computed every time.
j.precomputed = publicKey
return publicKey, nil
}
Parsing and validating a JWT from a JSON Web Key Set (JWKS).
I made this package to work with github.com/dgrijalva/jwt-go to more easily parse and validate JWTs with the most popular package.
Using your example URL, my_url, here's an example of how to parse and validate JWTs.
package main
import (
"log"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/MicahParks/keyfunc"
)
func main() {
// Get the JWKS URL.
jwksURL := "my_url"
// Create the keyfunc options. Refresh the JWKS every hour and log errors.
refreshInterval := time.Hour
options := keyfunc.Options{
RefreshInterval: &refreshInterval,
RefreshErrorHandler: func(err error) {
log.Printf("There was an error with the jwt.KeyFunc\nError: %s", err.Error())
},
}
// Create the JWKS from the resource at the given URL.
jwks, err := keyfunc.Get(jwksURL, options)
if err != nil {
log.Fatalf("Failed to create JWKS from resource at the given URL.\nError: %s", err.Error())
}
// Get a JWT to parse.
jwtB64 := "eyJhbGciOiJQUzM4NCIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMeDFGbWF5UDJZQnR4YXFTMVNLSlJKR2lYUktudzJvdjVXbVlJTUctQkxFIn0.eyJleHAiOjE2MTU0MDY5ODIsImlhdCI6MTYxNTQwNjkyMiwianRpIjoiMGY2NGJjYTktYjU4OC00MWFhLWFkNDEtMmFmZDM2OGRmNTFkIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL21hc3RlciIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJhZDEyOGRmMS0xMTQwLTRlNGMtYjA5Ny1hY2RjZTcwNWJkOWIiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJ0b2tlbmRlbG1lIiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiY2xpZW50SG9zdCI6IjE3Mi4yMC4wLjEiLCJjbGllbnRJZCI6InRva2VuZGVsbWUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC10b2tlbmRlbG1lIiwiY2xpZW50QWRkcmVzcyI6IjE3Mi4yMC4wLjEifQ.Rxrq41AxbWKIQHWv-Tkb7rqwel3sKT_R_AGvn9mPIHqhw1m7nsQWcL9t2a_8MI2hCwgWtYdgTF1xxBNmb2IW3CZkML5nGfcRrFvNaBHd3UQEqbFKZgnIX29h5VoxekyiwFaGD-0RXL83jF7k39hytEzTatwoVjZ-frga0KFl-nLce3OwncRXVCGmxoFzUsyu9TQFS2Mm_p0AMX1y1MAX1JmLC3WFhH3BohhRqpzBtjSfs_f46nE1-HKjqZ1ERrAc2fmiVJjmG7sT702JRuuzrgUpHlMy2juBG4DkVcMlj4neJUmCD1vZyZBRggfaIxNkwUhHtmS2Cp9tOcwNu47tSg"
// Parse the JWT.
token, err := jwt.Parse(jwtB64, jwks.KeyFunc)
if err != nil {
log.Fatalf("Failed to parse the JWT.\nError: %s", err.Error())
}
// Check if the token is valid.
if !token.Valid {
log.Fatalf("The token is not valid.")
}
log.Println("The token is valid.")
}
I am trying to implement JWT for REST
I'm just curious on this code below
var mySigningKey = []byte("mysecret")
token, err := jwt.Parse(r.Header["Token"][0], func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("There was an error")
}
return mySigningKey, nil
})
fmt.Println(token)
I expect token will return mySigningKey value but its return Token value
that shoud return value inside mySigningKey right?
Or did I misunderstand some concept?
By calling Parse method on your header Token value you are creating the token struct. If you want to get your SigningString you need to call SigningString() method on your token variable.
// Generate the signing string. This is the
// most expensive part of the whole deal. Unless you
// need this for something special, just go straight for
// the SignedString.
func (t *Token) SigningString() (string, error) {
var err error
parts := make([]string, 2)
for i, _ := range parts {
var source map[string]interface{}
if i == 0 {
source = t.Header
} else {
source = t.Claims
}
var jsonValue []byte
if jsonValue, err = json.Marshal(source); err != nil {
return "", err
}
parts[i] = EncodeSegment(jsonValue)
}
return strings.Join(parts, "."), nil
}
The function which is the second argument to Parse is responsible for providing the signing key which will be used to verify the JWT signature is correct.
In the case illustrated in the question, the signing key is a constant because HMAC is used to sign it.
However, if you had a RSA keypair, the token header would be useful to pick the right public key to check the signature, perhaps pulling it down from a well known JWKS endpoint.
func Login(c echo.Context) error {
user := &users.User{}
if err := c.Bind(&user); err != nil {
return err
}
return token.SigIn(c, user.Email, user.Password)
}
This is my Login function that retrieve the token when the user send the requests.
the Signin func that handle the token
func SigIn(c echo.Context, email, password string) error {
user := users.User{}
db := database.SetUp()
if err := db.Where("email = ?", email).First(&user).Error; gorm.IsRecordNotFoundError(err) {
restErr := errors.NewBadRequestError("Invalid credentials")
return c.JSON(http.StatusBadRequest, restErr)
}
if user.VerifyPassword(password) != nil {
restErr := errors.NewUnauthorizedError("Couldn't log you in with these credentials")
return c.JSON(http.StatusUnauthorized, restErr)
}
//user is successfull
return CreateToken(c)
}
the CreateToken func is as follow
type TokenJWT struct {
Token string `json:"token"`
}
func CreateToken(c echo.Context) error {
token := jwt.New(jwt.SigningMethodHS256)
claims := token.Claims.(jwt.MapClaims)
claims["authorized"] = true
claims["name"] = "Pascal Gaetan"
claims["exp"] = time.Now().Add(time.Hour * 1).Unix()
// Generate encoded token and send it as response.
t, err := token.SignedString([]byte("my_secret_key"))
if err != nil {
return err
}
return c.JSON(http.StatusOK, TokenJWT{
Token: t,
})
}
when everyhting is succesfull, i would like to get the authenticated user through an URL /api/me that calls a Me function
Let me split your question into two parts: the first one is how to easily encode and decode user in or from JWT token and the second part is how to write a generic code which can retrieve user from everywhere.
From your example I mentioned that you created a MapClaims but to reduce parsing complexity it will be better to create a token using a custom claims type. If you are using dgrijalva/jwt-go, then according to documentation you can do something like that
type UserClaims struct {
Name string `json:"name"`
jwt.StandardClaims
}
// encode it as before, but with your created type
t := jwt.New(signer)
userClaims := &UserClaims{Name: "Burmese"}
t.Claims = userClaims
tokenString, err = t.SignedString(]byte("my_secret_key"))
then you can parse your user in your router/framework middleware with
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c"
token, err := jwt.ParseWithClaims(tokenString, &UserClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte("my_secret_key"), nil
})
if claims, ok := token.Claims.(*UserClaims); ok && token.Valid {
fmt.Printf("%v %v", claims.Name, claims.StandardClaims.ExpiresAt)
} else {
fmt.Println(err)
}
This example was adopted from an official documentation here
Now you know how to parse authenticated user struct with ease and the next logic move is to wrap it into your middleware. Whether there are a lot of implementation details like you can retrieve JWT from cookie, header or query, also defining some ordering on them, the gist the following: you should have wrapped abovementioned code into your middleware and after parsing the struct you can pass it via your request context. I don't use echo and other frameworks, but for pure net/http you can pass your parsed struct from middleware with
context.WithValue(ctx, UserCtxKey, claims)
Hope it helps!
This is a fairly common design pattern to create an authenticated client and then call various action methods on it. You could do something like the following:
type Client struct {
... // other members
token string // unexported unless there is a special reason to do otherwise
}
func NewClient(c echo.Context, email, password string) (*Client, error) {
user := users.User{}
cl := Client{}
... // your original method
cl.token = token
return &cl, nil
}
func (c *Client) DoSomething(...) ... { ... }
I need to retrieve the values of sub-claims from a JWT in Go.
I have (legacy) JWTs I need to parse in go, which contain a custom claim "data" which holds an Json-Object consisting of some fields (userid, username), so
{ [...standard claims]..., "data":{"id":"123", "name":"JohnDoe"} }
With using github.com/dgrijalva/jwt-go, I can parse the token and access the claims with this:
keyfunc := func(token *jwt.Token) (interface{}, error) {
return tknkey, nil
}
tkn, err := jwt.Parse(tknStr, keyfunc)
cl, _ := tkn.Claims.(jwt.MapClaims)
This works fine for the standard claims, and I also get the field names from the Json-Sub-Object in the "data" claim, but not the field values (all empty strings). I also tried setting up structs matching the claim hierarchy (outer and inner struct), with no success.
What would be the way to access the values of the sub-claims?
You can use jwt.MapClaims with "data": map[string]string with the following steps.
Steps 1.1 and 1.2 create the token
Steps 2.1 and 2.2 parse the token and extract the sub-claim values.
In the below example, jwt is github.com/golang-jwt/jwt/v4. Running code for this example is at github.com/grokify/goauth/examples/jwt/main.go.
Step 1.1: Create the claims
Create the custom MapClaims with a data map. Add a custom data.name property which we'll extract below.
claims := &jwt.MapClaims{
"iss": "issuer",
"exp": time.Now().Add(time.Hour).Unix(),
"data": map[string]string{
"id": "123",
"name": "JohnDoe",
},
}
Step 1.2: Create the JWT
For this example, we'll use a symmetric key.
token := jwt.NewWithClaims(
jwt.SigningMethodHS256,
claims)
secretKey := "foobar"
tokenString, err := token.SignedString([]byte(secretKey))
Step 2.1: Parse the token and cast claims to MapClaims.
Use the secretKey again since this example uses HS256.
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte(secretKey), nil
})
claims := token.Claims.(jwt.MapClaims)
Step 2.2: Extract custom sub-claim
Cast data to map[string]interface{} and cast data["name"] to string.
data := claims["data"].(map[string]interface{})
name := data["name"].(string)
Try this
type UserData struct {
Id string `json:"id"`
Name string `json:"name"`
}
type JWTClaim struct {
Data UserData `json:"data"`
jwt.StandardClaims
}
token, err := jwt.ParseWithClaims(
signedToken,
&JWTClaim{},
func(token *jwt.Token) (interface{}, error) {
return []byte(jwtKey), nil
},
)