A fast and simple JWT implementation for Go

JWT

build status gocov report card godocs

Fast and simple JWT implementation written in Go. This package was designed with security, performance and simplicity in mind, it protects your tokens from critical vulnerabilities that you may find in other libraries.

Benchmarks Total Repetitions - higher is better

Please star this open source project to attract more developers so that together we can improve it even more!

Installation

The only requirement is the Go Programming Language.

$ go get github.com/kataras/jwt

Import as import "github.com/kataras/jwt" and use it as jwt.XXX.

Table of Contents

Getting Started

Sign and generate a token with the Sign method, returns the token in compact form. Optionally set an expiration, if "exp" is missing from the payload use the jwt.MaxAge helper. Verify the token with the Verify method, returns a VerifiedToken value. Decode the custom claims with the VerifiedToken.Claims method. Extremely easy!

package main

import (
	"time"

	"github.com/kataras/jwt"
)

// Keep it secret.
var sharedKey = []byte("sercrethatmaycontainch@r$32chars")

type FooClaims struct {
	Foo string `json:"foo"`
}

func main() {
	// Generate a token which expires at 15 minutes from now:
	myClaims := FooClaims{
		Foo: "bar",
	} // can be a map too.

	token, err := jwt.Sign(jwt.HS256, sharedKey, myClaims, jwt.MaxAge(15*time.Minute))
	if err != nil {
		panic(err)
	}

	// Verify and extract claims from a token:
	verifiedToken, err := jwt.Verify(jwt.HS256, sharedKey, token)
	if err != nil {
		panic(err)
	}

	var claims FooClaims
	err = verifiedToken.Claims(&claims)
	if err != nil {
		panic(err)
	}

	print(claims.Foo)
}

The package contains comments on each one of its exported functions, structures and variables, therefore, for a more detailed technical documentation please refer to godocs.

Sign a Token

Signing and Verifying a token is an extremely easy process.

Signing a Token is done through the Sign package-level function.

var sharedKey = []byte("sercrethatmaycontainch@r$32chars")
type User struct {
    Username string `json:"username"`
}
userClaims := User {
    Username:"kataras",
}

token, err := jwt.Sign(jwt.HS256, sharedkey, userClaims, jwt.MaxAge(15 *time.Minute))

[1] The first argument is the signing algorithm to create the signature part. [2] The second argument is the private key (or shared key, when symmetric algorithm was chosen) will be used to create the signature. [3] The third argument is the JWT claims. The JWT claims is the payload part and it depends on your application's requirements, there you can set custom fields (and expiration) that you can extract to another request of the same authorized client later on. Note that the claims can be any Go type, including custom struct, map and raw []byte. [4] The last variadic argument is a type of SignOption (MaxAge function and Claims struct are both valid sign options), can be used to merge custom claims with the standard ones. Returns the encoded token, ready to be sent and stored to the client.

The jwt.MaxAge is a helper which sets the jwt.Claims.Expiry and jwt.Claims.IssuedAt for you.

Example Code to manually set all claims using a standard map:

now := time.Now()
claims := map[string]interface{}{
    "iat": now.Unix(),
    "exp": now.Add(15 * time.Minute).Unix(),
    "foo": "bar",
}

token, err := jwt.Sign(jwt.HS256, sharedKey, claims)

See SignWithHeader too.

Example Code to merge map claims with standard claims:

customClaims := jwt.Map{"foo": "bar"}

now := time.Now()
standardClaims := jwt.Claims{
    Expiry:   now.Add(15 * time.Minute).Unix(),
    IssuedAt: now.Unix(), 
    Issuer:   "my-app",
}

token, err := jwt.Sign(jwt.HS256, sharedKey, customClaims, standardClaims)

The jwt.Map is just a type alias, a shortcut, of map[string]interface{}.

At all cases, the iat(IssuedAt) and exp(Expiry/MaxAge) (and nbf(NotBefore)) values will be validated automatically on the Verify method.

Example Code to Sign & Verify a non-JSON payload:

token, err := jwt.Sign(jwt.HS256, sharedkey, []byte("raw payload - no json here"))

If the payload is not a JSON one, then merging with standard claims is not possible, therefore options like jwt.MaxAge are not available.

verifiedToken, err := jwt.Verify(jwt.HS256, sharedKey, token, jwt.Plain)
// verifiedToken.Payload == raw contents

Again, if the received payload is not a JSON one, options like jwt.Expected or jwt.NewBlocklist are not available as well.

The standard JWT Claims

The jwt.Claims we've shown above, looks like this:

type Claims struct {
    // The opposite of the exp claim. A number representing a specific
    // date and time in the format “seconds since epoch” as defined by POSIX.
    // This claim sets the exact moment from which this JWT is considered valid.
    // The current time (see `Clock` package-level variable)
    // must be equal to or later than this date and time.
    NotBefore int64 `json:"nbf,omitempty"`

    // A number representing a specific date and time (in the same
    // format as exp and nbf) at which this JWT was issued.
    IssuedAt int64 `json:"iat,omitempty"`

    // A number representing a specific date and time in the
    // format “seconds since epoch” as defined by POSIX6.
    // This claims sets the exact moment from which
    // this JWT is considered invalid. This implementation
    // allow for a certain skew between clocks
    // (by considering this JWT to be valid for a few minutes
    // after the expiration date, modify the `Clock` variable).
    Expiry int64 `json:"exp,omitempty"`

    // A string representing a unique identifier for this JWT.
    // This claim may be used to differentiate JWTs with
    // other similar content (preventing replays, for instance).
    ID string `json:"jti,omitempty"`

    // A string or URI that uniquely identifies the party
    // that issued the JWT.
    // Its interpretation is application specific
    // (there is no central authority managing issuers).
    Issuer string `json:"iss,omitempty"`

    // A string or URI that uniquely identifies the party
    // that this JWT carries information about.
    // In other words, the claims contained in this JWT
    // are statements about this party.
    // The JWT spec specifies that this claim must be unique in
    // the context of the issuer or,
    // in cases where that is not possible, globally unique. Handling of
    // this claim is application specific.
    Subject string `json:"sub,omitempty"`

    // Either a single string or URI or an array of such
    // values that uniquely identify the intended recipients of this JWT.
    // In other words, when this claim is present, the party reading
    // the data in this JWT must find itself in the aud claim or
    // disregard the data contained in the JWT.
    // As in the case of the iss and sub claims, this claim is
    // application specific.
    Audience []string `json:"aud,omitempty"`
}

Verify a Token

Verifying a Token is done through the Verify package-level function.

verifiedToken, err := jwt.Verify(jwt.HS256, sharedKey, token)

See VerifyWithHeaderValidator too.

The VerifiedToken carries the token decoded information:

type VerifiedToken struct {
    Token          []byte // The original token.
    Header         []byte // The header (decoded) part.
    Payload        []byte // The payload (decoded) part.
    Signature      []byte // The signature (decoded) part.
    StandardClaims Claims // Standard claims extracted from the payload.
}

Decode custom Claims

To extract any custom claims, given on the Sign method, we use the result of the Verify method, which is a VerifiedToken pointer. This VerifiedToken has a single method, the Claims(dest interface{}) error one, which can be used to decode the claims (payload part) to a value of our choice. Again, that value can be a map or any struct.

var claims = struct {
    Foo string `json:"foo"`
}{} // or a map.

err := verifiedToken.Claims(&claims)

By default expiration set and validation is done through time.Now(). You can change that behavior through the jwt.Clock variable, e.g.

jwt.Clock = time.Now().UTC()

JSON required tag

When more than one token with different claims can be generated based on the same algorithm and key, somehow you need to invalidate a token if its payload misses one or more fields of your custom claims structure. Although it's not recommended to use the same algorithm and key for generating two different types of tokens, you can do it, and to avoid invalid claims to be retrieved by your application's route handler this package offers the JSON ,required tag field. It checks if the claims extracted from the token's payload meet the requirements of the expected struct value.

The first thing we have to do is to change the default jwt.Unmarshal variable to the jwt.UnmarshalWithRequired, once at the init of the application:

func init() {
    jwt.Unmarshal = jwt.UnmarshalWithRequired
}

The second thing, is to add the ,required json tag field to our struct, e.g.

type userClaims struct {
    Username string `json:"username,required"`
}

That's all, the VerifiedToken.Claims method will throw an ErrMissingKey if the given token's payload does not meet the requirements.

Standard Claims Validators

A more performance-wise alternative to json:"XXX,required" is to add validators to check the standard claims values through a TokenValidator or to check the custom claims manually after the VerifiedToken.Claims method.

The TokenValidator interface looks like this:

type TokenValidator interface {
	ValidateToken(token []byte, standardClaims Claims, err error) error
}

The last argument of Verify/VerifyEncrypted optionally accepts one or more TokenValidator. Available builtin validators:

  • Leeway(time.Duration)
  • Expected
  • Blocklist

The Leeway adds validation for a leeway expiration time. If the token was not expired then a comparison between this "leeway" and the token's "exp" one is expected to pass instead (now+leeway > exp). Example of use case: disallow tokens that are going to be expired in 3 seconds from now, this is useful to make sure that the token is valid when the when the user fires a database call:

verifiedToken, err := jwt.Verify(jwt.HS256, sharedKey, token, jwt.Leeway(3*time.Second))
if err != nil {
   // err == jwt.ErrExpired
}

The Expected performs simple checks between standard claims values. For example, disallow tokens that their "iss" claim does not match the "my-app" value:

verifiedToken, err := jwt.Verify(jwt.HS256, sharedKey, token, jwt.Expected{
    Issuer: "my-app",
})
if err != nil {
    // errors.Is(jwt.ErrExpected, err)
}

Block a Token

When a user logs out, the client app should delete the token from its memory. This would stop the client from being able to make authorized requests. But if the token is still valid and somebody else has access to it, the token could still be used. Therefore, a server-side invalidation is indeed useful for cases like that. When the server receives a logout request, take the token from the request and store it to the Blocklist through its InvalidateToken method. For each authorized request the jwt.Verify will check the Blocklist to see if the token has been invalidated. To keep the search space small, the expired tokens are automatically removed from the Blocklist's in-memory storage.

Enable blocklist by following the three simple steps below.

1. Initialize a blocklist instance, clean unused and expired tokens every 1 hour.

blocklist := jwt.NewBlocklist(1 * time.Hour)

2. Add the blocklist instance to the jwt.Verify's last argument, to disallow blocked entries.

verifiedToken, err := jwt.Verify(jwt.HS256, sharedKey, token, blocklist)
// [err == jwt.ErrBlocked when the token is valid but was blocked]

3. Call the blocklist.InvalidateToken whenever you want to block a specific authorized token. The method accepts the token and the expiration time should be removed from the blocklist.

blocklist.InvalidateToken(verifiedToken.Token, verifiedToken.StandardClaims)

By default the unique identifier is retrieved through the "jti" (Claims{ID}) and if that it's empty then the raw token is used as the map key instead. To change that behavior simply modify the blocklist.GetKey field before the InvalidateToken method.

Token Pair

A Token pair helps us to handle refresh tokens. It is a structure which holds both Access Token and Refresh Token. Refresh Token is long-live and access token is short-live. The server sends both of them at the first contact. The client uses the access token to access an API. The client can renew its access token by hitting a special REST endpoint to the server. The server verifies the refresh token and optionally the access token which should return ErrExpired, if it's expired or going to be expired in some time from now (Leeway), and renders a new generated token to the client. There are countless resources online and different kind of methods for using a refresh token. This jwt package offers just a helper structure which holds both the access and refresh tokens and it's ready to be sent and received to and from a client.

type ClientClaims struct {
    ClientID string `json:"client_id"`
}
accessClaims := ClientClaims{ClientID: "client-id"}
accessToken, err := jwt.Sign(alg, secret, accessClaims, jwt.MaxAge(10*time.Minute))
if err != nil {
    // [handle error...]
}

refreshClaims := jwt.Claims{Subject: "client", Issuer: "my-app"}
refreshToken, err := jwt.Sign(alg, secret, refreshClaims, jwt.MaxAge(time.Hour))
if err != nil {
    // [handle error...]
}

tokenPair := jwt.NewTokenPair(accessToken, refreshToken)

The tokenPair is JSON-compatible value, you can render it to a client and read it from a client HTTP request.

JSON Web Algorithms

There are several types of signing algorithms available according to the JWA(JSON Web Algorithms) spec. The specification requires a single algorithm to be supported by all conforming implementations:

  • HMAC using SHA-256, called HS256 in the JWA spec.

The specification also defines a series of recommended algorithms:

  • RSASSA PKCS1 v1.5 using SHA-256, called RS256 in the JWA spec.
  • ECDSA using P-256 and SHA-256, called ES256 in the JWA spec.

The implementation supports all of the above plus RSA-PSS and the new Ed25519. Navigate to the alg.go source file for details. In-short:

Algorithm jwt.Sign jwt.Verify
jwt.HS256 / HS384 / HS512 []byte The same sign key
jwt.RS256 / RS384 / RS512 *rsa.PrivateKey *rsa.PublicKey
jwt.PS256 / PS384 / PS512 *rsa.PrivateKey *rsa.PublicKey
jwt.ES256 / ES384 / ES512 *ecdsa.PrivateKey *ecdsa.PublicKey
jwt.EdDSA ed25519.PrivateKey ed25519.PublicKey

Choose the right Algorithm

Choosing the best algorithm for your application needs is up to you, however, my recommendations follows.

  • Already work with RSA public and private keys? Choose RSA(RS256/RS384/RS512/PS256/PS384/PS512) (length of produced token characters is bigger).
  • If you need the separation between public and private key, choose ECDSA(ES256/ES384/ES512) or EdDSA. ECDSA and EdDSA produce smaller tokens than RSA.
  • If you need performance and well-tested algorithm, choose HMAC(HS256/HS384/HS512) - the most common method.

The basic difference between symmetric and an asymmetric algorithm is that symmetric uses one shared key for both signing and verifying a token, and the asymmetric uses private key for signing and a public key for verifying. In general, asymmetric data is more secure because it uses different keys for the signing and verifying process but it's slower than symmetric ones.

Use your own Algorithm

If you ever need to use your own JSON Web algorithm, just implement the Alg interface. Pass it on jwt.Sign and jwt.Verify functions and you're ready to GO.

Generate keys

Keys can be generated via OpenSSL or through Go's standard library.

import (
    "crypto/rand"
    "crypto/rsa"
    "crypto/elliptic"
    "crypto/ed25519"
)
// Generate HMAC
sharedKey := make([]byte, 32)
_, _ = rand.Read(sharedKey)

// Generate RSA
bitSize := 2048
privateKey, _ := rsa.GenerateKey(rand.Reader, bitSize)
publicKey := &privateKey.PublicKey

// Generace ECDSA
c := elliptic.P256()
privateKey, _ := ecdsa.GenerateKey(c, rand.Reader)
publicKey := &privateKey.PublicKey

// Generate EdDSA
publicKey, privateKey, _ := ed25519.GenerateKey(rand.Reader)

Load and Parse keys

This package contains all the helpers you need to load and parse PEM-formatted keys.

All the available helpers:

// HMAC
MustLoadHMAC(filenameOrRaw string) []byte
// RSA
MustLoadRSA(privFile, pubFile string) (*rsa.PrivateKey, *rsa.PublicKey)
LoadPrivateKeyRSA(filename string) (*rsa.PrivateKey, error)
LoadPublicKeyRSA(filename string) (*rsa.PublicKey, error) 
ParsePrivateKeyRSA(key []byte) (*rsa.PrivateKey, error)
ParsePublicKeyRSA(key []byte) (*rsa.PublicKey, error)
// ECDSA
MustLoadECDSA(privFile, pubFile string) (*ecdsa.PrivateKey, *ecdsa.PublicKey)
LoadPrivateKeyECDSA(filename string) (*ecdsa.PrivateKey, error)
LoadPublicKeyECDSA(filename string) (*ecdsa.PublicKey, error) 
ParsePrivateKeyECDSA(key []byte) (*ecdsa.PrivateKey, error)
ParsePublicKeyECDSA(key []byte) (*ecdsa.PublicKey, error)
// EdDSA
MustLoadEdDSA(privFile, pubFile string) (ed25519.PrivateKey, ed25519.PublicKey)
LoadPrivateKeyEdDSA(filename string) (ed25519.PrivateKey, error)
LoadPublicKeyEdDSA(filename string) (ed25519.PublicKey, error)
ParsePrivateKeyEdDSA(key []byte) (ed25519.PrivateKey, error)
ParsePublicKeyEdDSA(key []byte) (ed25519.PublicKey, error)

Example Code:

import "github.com/kataras/jwt"
privateKey, publicKey := jwt.MustLoadEdDSA("./private_key.pem", "./public_key.pem")
claims := jwt.Map{"foo": "bar"}
maxAge := jwt.MaxAge(15 * time.Minute)

token, err := jwt.Sign(jwt.EdDSA, privateKey, claims, maxAge)
verifiedToken, err := Verify(EdDSA, publicKey, token)

Embedded keys? No problem, just integrate the jwt.ReadFile variable which is just a type of func(filename string) ([]byte, error).

Encryption

JWE (encrypted JWTs) is outside the scope of this package, a wire encryption of the token's payload is offered to secure the data instead. If the application requires to transmit a token which holds private data then it needs to encrypt the data on Sign and decrypt on Verify. The SignEncrypted and VerifyEncrypted package-level functions can be called to apply any type of encryption.

The package offers one of the most popular and common way to secure data; the GCM mode + AES cipher. We follow the encrypt-then-sign flow which most researchers recommend (it's safer as it prevents padding oracle attacks).

In-short, you need to call the jwt.GCM and pass its result to the jwt.SignEncrypted and jwt.VerifyEncrypted:

// Replace with your own keys and keep them secret.
// The "encKey" is used for the encryption and
// the "sigKey" is used for the selected JSON Web Algorithm
// (shared/symmetric HMAC in that case).
var (
    encKey = MustGenerateRandom(32)
    sigKey = MustGenerateRandom(32)
)

func main(){
    encrypt, decrypt, err := GCM(encKey, nil)
    if err != nil {
        // [handle error...]
    }
    // Encrypt and Sign the claims:
    token, err := SignEncrypted(jwt.HS256, sigKey, encrypt, claims, jwt.MaxAge(15 * time.Minute))
    // [...]

    // Verify and decrypt the claims:
    verifiedToken, err := VerifyEncrypted(jwt.HS256, sigKey, decrypt, token)
    // [...]
}

Read more about GCM at: https://en.wikipedia.org/wiki/Galois/Counter_Mode

References

Here is what helped me to implement JWT in Go:

License

This software is licensed under the MIT License.

Owner
Gerasimos (Makis) Maropoulos
🥇 That Greek Gopher | 💨 Senior Backend Engineer at PNOĒ | 🎓My dream is to create an international IT university that will produce flawless developers!
Gerasimos (Makis) Maropoulos
Comments
  • Header decode compatibility issue

    Header decode compatibility issue

    I currently have a problem with header decode. I have 2 services, one in Golang and one in Java. The JWT token is created on Java service using jjwt library (https://github.com/jwtk/jjwt). The header after decode is, for e.g: {"typ":"JWT","alg":"RS256"} but currently in this library you are hardcode it as following:

    func createHeaderRaw(alg string) []byte {
    	if header := fixedHeaders[alg]; header != nil {
    		return header.raw
    	}
    
    	return []byte(`{"alg":"` + alg + `","typ":"JWT"}`)
    }
    

    which produce output: {"alg":"RS256","typ":"JWT"}. Clearly the content are the same but the check is failed since you are compare them byte by byte. I would suggest another check which using map. I can create a pull request if it's possible. Thank you for your great work.

  • jwt: unexpected token algorithm

    jwt: unexpected token algorithm "Error"

    i've test the token and public on jwt and it works fine but when i try it with this pkg i get "jwt: unexpected token algorithm"

    verifiedToken, err := jwt.Verify(jwt.RS512, publicKEY, []byte(token)) check this test jwt

  • CompareHeader function fails when key id (kid) included

    CompareHeader function fails when key id (kid) included

    $ go version
    go version go1.17 darwin/amd64
    
    $ cat go.mod | grep kataras
    	github.com/kataras/iris/v12 v12.2.0-alpha2.0.20210717090056-b2cc3a287149
    

    The header block returned from Auth0 includes a key id (kid). This causes the exact byte match in the compareHeader func to fail.

    {
      "alg": "RS256",
      "typ": "JWT",
      "kid": "<REDACT>"
    }
    

    https://github.com/kataras/jwt/blob/main/token.go#L229-L232

  • [BUG] jwt Claims - Invalid for individual custom struct fields

    [BUG] jwt Claims - Invalid for individual custom struct fields

    main.go :

    app := iris.New()
    
    app.UseRouter(accesslog.New(app.Logger().Printer).Handler)
    app.UseRouter(recover2.New())
    
    app.Get("kf/auth", api.Auth)
    app.Use(api.VerifyMiddleware)
    
    app.Get("/test", func(c iris.Context) {
        admin := jwt.Get(c).(*tables.AdminUser)
        // test - Is inconsistent with the AdminUser given in auth,types.NullString All defaults。
        // &{1 { false} { false} { false} { false} { false} { false} { false} 1 3 { false} 2010-08-15 00:37:37 { false} 0 { false} { false} { false} 0}
        fmt.Println("test:", admin)
    })
    
    _ = app.Listen(":188")
    

    api.Auth :

    func Auth(c iris.Context)  {
        // ...
        var user tables.AdminUser
        if err = db.Where(squirrel.Eq{"username": splits[0], "password": splits[1]}).
            First(&user).Error; err != nil {
            c.StopWithError(iris.StatusInternalServerError, err)
            return
        }
    
        // auth: 
        // {1 {admin true} {jdw123 true} {|0| true} {符之云 true} { true} {20200414093409 true} {test pid str true} 1 3 {1 true} 2010-08-15 00:37:37 { true} 0 { true} { true} { true} 0}
        fmt.Println("auth:", user)
    
        token, err := signer.Sign(user)
        if err != nil {
            c.StopWithError(iris.StatusInternalServerError, err)
            return
        }
    
        _, _ = c.Write(token)
    }
    

    AdminUser :

    type AdminUser struct {
        ID         int              `gorm:"column:ID;type:int;primaryKey" json:"ID"`
        Username   types.NullString `gorm:"column:username;type:varchar(50)" json:"username"`
        Password   types.NullString `gorm:"column:password;type:varchar(50)" json:"password"`
        Role       types.NullString `gorm:"column:role;type:varchar(255)" json:"role"`
        People     types.NullString `gorm:"column:people;type:varchar(60)" json:"people"`
        Area       types.NullString `gorm:"column:area;type:varchar(50)" json:"area"`
        EditTime   types.NullString `gorm:"column:edit_time;type:varchar(50)" json:"edit_time"`
        PIDSTR     types.NullString `gorm:"column:PIDSTR;type:varchar(200)" json:"PIDSTR"`
        Bmid       int              `gorm:"column:bmid;type:int;default:0" json:"bmid"`
        Bmgid      int              `gorm:"column:bmgid;type:int;default:0" json:"bmgid"`
        Kind       types.NullString `gorm:"column:kind;type:varchar(100)" json:"kind"`
        InsertTime types.Time       `gorm:"column:insert_time;type:datetime;default:getdate()" json:"insert_time"`
        ProvStr    types.NullString `gorm:"column:prov_str;type:varchar(200)" json:"prov_str"`
        CheckPID   int              `gorm:"column:CheckPID;type:int;default:0" json:"CheckPID"`
        Sname      types.NullString `gorm:"column:sname;type:varchar(20)" json:"sname"`
        UserQQ     types.NullString `gorm:"column:user_qq;type:varchar(20)" json:"user_qq"`
        UserMobile types.NullString `gorm:"column:user_mobile;type:varchar(20)" json:"user_mobile"`
        Issyn      int              `gorm:"column:issyn;type:int;default:0" json:"issyn"`
    }
    

    But I found that there is a value for types.Time (2010-08-15 00:37:37),At the time of request, I found that it was assigned by UnmarshalJSON, but the output types.NullString is always ""

    type NullString struct {
        sql.NullString
    }
    
    func (v *NullString) Scan(value interface{}) error {
        v.Valid = value == nil
        if s, ok := value.(string); ok && v.Valid {
            v.String = s
        }
        return nil
    }
    
    func (v NullString) Value() (driver.Value, error) {
        if !v.Valid {
            return nil, nil
        }
        return v.String, nil
    }
    
    func (v NullString) MarshalJSON() ([]byte, error) {
        return json.Marshal(v.String)
    }
    
    func (v NullString) UnmarshalJSON(data []byte) error {
        var s string
        if err := json.Unmarshal(data, &s); err != nil {
            return err
        }
        if v.Valid = len(s) > 0; v.Valid {
            v.String = s // s = ""
        }
        return nil
    }
    
  • Fix or remove Audience Claim from standard claims

    Fix or remove Audience Claim from standard claims

    According to RFC 7519

    In the general case, the "aud" value is an array of case-sensitive strings, each containing a StringOrURI value. In the special case when the JWT has one audience, the "aud" value MAY be a single case-sensitive string containing a StringOrURI value.

    Currently in the code it is

    Audience []string `json:"aud,omitempty"`
    

    Since it could be an array or a string, and the implementation is application-specific please deserialize in a generic manner or even skip the claim.

  • ignore expired tokens on certain routes

    ignore expired tokens on certain routes

    This library is not supported skip expiration dates for token validation, there is a crucial need for this feature especially in revoking access tokens and refreshing tokens for expired access tokens.

    Could you please support this feature?

  • payload is not a type of JSON error

    payload is not a type of JSON error

    Hi there, I'm having some issues with Verify func. The following throws an error of: payload is not a type of JSON. I don't see anything wrong with that token. Could be this a bug?

    package main
    
    import (
      "fmt"
      "github.com/kataras/jwt"
    )
    
    func main() {
      secret := "secret"
      token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEyMywibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.QzFnWiase0tPyeNzn8ecl-kVfDVEZ1ctbf9ztM0Qjqg"
    
      _, err := jwt.Verify(jwt.HS256, []byte(secret), []byte(token))
    	if err != nil {
    		fmt.Printf("JWT verify error: %v", err)
    		return
    	}
    
      fmt.Println("Verified")
    }
    
  • "Unexpected token algorithm" error when validating CloudFlare's JWT

    Hello, I'm trying to validate Cloudflare Zero Trust JWT, but I'm getting the error "jwt: unexpected token algorithm". Looking at the code, I think the problem is related to this line the compares the header, since the JWT from CF doesn't have the "typ" field.

    The line: https://github.com/kataras/jwt/blob/d03e03aaf19da85fc083eccc404d8f102e1c1641/token.go#L169

    Cloudflare doc: https://developers.cloudflare.com/cloudflare-one/identity/authorization-cookie/validating-json/

    Thank you.

  • JWT not rejected when audience claim present but not specified by verifying party

    JWT not rejected when audience claim present but not specified by verifying party

    Spec:

    Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the "aud" claim when this claim is present, then the JWT MUST be rejected

    I interpret the above as:

    When JWT has an aud claim present, the code calling jwt.Verify() must also specify an audience key that must be one of the items in JWT's aud array.

    I would expect this to fail:

    jwt.Verify(jwt.EdDSA, signerPubKey, tokenBytes)
    

    But instead I need to add to get verification to fail when there's an audience mismatch:

    jwt.Verify(jwt.EdDSA, signerPubKey, tokenBytes, jwt.Expected{
        Audience: jwt.Audience{"bar"},
    })
    

    My concrete use case: I have an single-sign on server for a multi-tenant app. The aud is the tenant ID, and is always present when the SSO server issues the JWT.

    Now if I have code that forgets to add jwt.Expected{Audience: jwt.Audience{"bar"},}, then I'll accidentally accept JWT for any tenant, when JWT spec compliant library would've protected me from processing a JWT that I wasn't the audience for.

    e.g. tenant 2's user 4's JWT works for tenant 3 as well, accessing data for a different user because I forgot to specify the audience. A spec-compliant JWT library would prevent this accident.

  • Audience parsing doesn't reject unexpected items

    Audience parsing doesn't reject unexpected items

    Because there's no default: branch erroring out, I could smuggle any valid JSON there, like true, false, null, any number or an object.

    https://github.com/kataras/jwt/blob/1639fcff96f82f7ff118fcff6e1fbd0e01754f2c/claims.go#L69

    They'll simply be discarded.

    I don't know if there's security implications, might not because because header and payload are signed by the issuer, and any tampering would get detected. But since I can't for 100 % say it's a non-issue, I'd feel more comfortable rejecting the data that would get discarded otherwise.

    WDYT?

  • Unnecessary custom base64 implementation

    Unnecessary custom base64 implementation

    Go has RawStdEncoding which makes this unnecessary:

    https://github.com/kataras/jwt/blob/1639fcff96f82f7ff118fcff6e1fbd0e01754f2c/token.go#L247

    Not trying to nitpick here, rather I think security-wise it's dangerous as a concept to mutate untrusted input data before it's fed to a signature validation algorithm

  • How to refresh jwt token?

    How to refresh jwt token?

    https://github.com/kataras/jwt#token-pair How to refresh jwt token? From this help document, it seems that I don't see how to use it. Can you give a specific http web demo or how to refresh the token.

A fast and simple JWT implementation for Go
A fast and simple JWT implementation for Go

JWT Fast and simple JWT implementation written in Go. This package was designed with security, performance and simplicity in mind, it protects your to

Jan 5, 2023
Go-gin-jwt - Secure web api using jwt token and caching mechanism

Project Description This project demonstrate how to create api and secure it wit

Jan 27, 2022
Krakend-jwt-header-rewriter - Kraken Plugin - JWT Header Rewriter

Kraken Plugin - JWT Header Rewriter 1 Plugin Configuration Name Desciption Defau

Feb 15, 2022
simple-jwt-provider - Simple and lightweight provider which exhibits JWTs, supports login, password-reset (via mail) and user management.

Simple and lightweight JWT-Provider written in go (golang). It exhibits JWT for the in postgres persisted user, which can be managed via api. Also, a password-reset flow via mail verification is available. User specific custom-claims also available for jwt-generation and mail rendering.

Dec 18, 2022
backend implementation demonstration in go with JWT, MongoDB and etc.

backend implementation demonstration in go with JWT, MongoDB and etc.

Nov 19, 2022
Golang implementation of JWT and Refresh Token

Fiber and JWT with Refresh Token Repo ini adalah demostrasi JWT support refresh token tanpa menggunakan storage Branch Main: unlimited refresh token R

Dec 18, 2022
An implementation of JOSE standards (JWE, JWS, JWT) in Go

Go JOSE Package jose aims to provide an implementation of the Javascript Object Signing and Encryption set of standards. This includes support for JSO

Dec 18, 2022
This is an implementation of JWT in golang!

jwt This is a minimal implementation of JWT designed with simplicity in mind. What is JWT? Jwt is a signed JSON object used for claims based authentic

Oct 25, 2022
Golang implementation of JSON Web Tokens (JWT)

jwt-go A go (or 'golang' for search engine friendliness) implementation of JSON Web Tokens NEW VERSION COMING: There have been a lot of improvements s

Jan 6, 2023
A simple user identify template with jwt token and gin, toy project

Simple Docs Register url : /api/auth/register param type value name string username password string password mailbox string mailbox response: { "sta

Dec 31, 2021
Simple JWT Golang

sjwt Simple JSON Web Token - Uses HMAC SHA-256 Example // Set Claims claims := New() claims.Set("username", "billymister") claims.Set("account_id", 86

Dec 8, 2022
A simple authentication web application in Golang (using jwt)

Simple Authentication WebApp A simple authentication web app in Go (using JWT) Routes Path Method Data /api/v1/auth/register POST {"firstname":,"lastn

Feb 6, 2022
Golang with JWT, Go Gin and MongoDB

User authentication in Golang with JWT, Go Gin and MongoDB Golang backend application that uses JWT tokens for users Locally Up Setup your .env file,

Sep 16, 2022
A demo of authentication and authorization using jwt
A demo of authentication and authorization using jwt

Nogopy Hi, this a demo of how to use jwt for authentication in microservices Keep in mind that this is a demo of how to authenticate using jwt, we don

Nov 1, 2021
JWT and Permission Middleware with MongoRPC

JWT and Permission Middleware with MongoRPC

Nov 19, 2021
Backend Development Rest Api Project for book management system. Used Features like redis, jwt token,validation and authorization.

Golang-restapi-project Simple Rest Api Project with Authentication, Autherization,Validation and Connection with redis File Structure ├── cache │ ├──

May 25, 2022
Generate and verify JWT tokens with Trusted Platform Module (TPM)

golang-jwt for Trusted Platform Module (TPM) This is just an extension for go-jwt i wrote over thanksgiving that allows creating and verifying JWT tok

Oct 7, 2022
Golang based User creation and Management application. GORM, Fiber, JWT

User Creation and Management app (BACK-END) Auth Features: Create Clients (regular password + 6 one-time passwords + (optional) QR code and Secret for

Dec 2, 2022
rEST API to test JWT on RS256 and HS256 algorithm.

JWT Check - Mock para tests This repo is just a simple example of JWT token generation using RS256 algorithm and HS256 algorithm. This api is responsi

Aug 19, 2022