Skip to content

Commit

Permalink
Merge pull request #21 from gbrlsnchs/v3
Browse files Browse the repository at this point in the history
V3
  • Loading branch information
Gabriel Sanches authored Feb 21, 2019
2 parents def8ed7 + 4dd8dfd commit c67b2ee
Show file tree
Hide file tree
Showing 43 changed files with 765 additions and 645 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
tags
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ sudo: false

language: 'go'
go:
- '1.10'
- '1.11'
- '1.12beta2'

install:
- 'cd $GOPATH'
Expand Down
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,30 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- Signing and verifying using [RSA-PSS](https://en.wikipedia.org/wiki/Probabilistic_signature_scheme).
- Signing and verifying using [Ed25519](https://ed25519.cr.yp.to/).
- `Audience` type for handling the `aud` claim [according to the RFC](https://tools.ietf.org/html/rfc7519#section-4.1.3).
- `Size` method to `Signer` interface.
- `Verifier` interface.
- `RawToken` type.
- SHA constants.

### Changed
- Improve performance by storing SHA hash functions in `sync.Pool`.
- Restructure `JWT` type by putting claims fields in a new struct.
- Change signing/verifying methods constructors' names.
- Unify signign/verifying methods constructors.
- Change `Signer` interface.
- Sign tokens with global function `Sign`.
- Verify tokens with global function `Verify`.

### Removed
- Support for `go1.10`.
- `Marshal` and `Unmarshal` functions.
- `Marshaler` and `Unmarshaler` interfaces.

## [2.0.0] - 2018-09-14
### Added
- `Parse` and `ParseBytes` functions.
Expand Down Expand Up @@ -118,6 +142,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- This package's source code, including examples and tests.
- Go dep files.

[Unreleased]: https://github.com/gbrlsnchs/jwt/compare/v2.0.0...HEAD
[2.0.0]: https://github.com/gbrlsnchs/jwt/compare/v1.1.0...v2.0.0
[1.1.0]: https://github.com/gbrlsnchs/jwt/compare/v1.0.2...v1.1.0
[1.0.2]: https://github.com/gbrlsnchs/jwt/compare/v1.0.1...v1.0.2
Expand Down
102 changes: 44 additions & 58 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![Build Status](https://travis-ci.org/gbrlsnchs/jwt.svg?branch=master)](https://travis-ci.org/gbrlsnchs/jwt)
[![Sourcegraph](https://sourcegraph.com/github.com/gbrlsnchs/jwt/-/badge.svg)](https://sourcegraph.com/github.com/gbrlsnchs/jwt?badge)
[![GoDoc](https://godoc.org/github.com/gbrlsnchs/jwt?status.svg)](https://godoc.org/github.com/gbrlsnchs/jwt)
[![Minimal Version](https://img.shields.io/badge/minimal%20version-go1.10%2B-5272b4.svg)](https://golang.org/doc/go1.10)
[![Minimal Version](https://img.shields.io/badge/compatible%20with-go1.11%2B-5272b4.svg)](https://golang.org/doc/go1.11)
[![Join the chat at https://gitter.im/gbrlsnchs/jwt](https://badges.gitter.im/gbrlsnchs/jwt.svg)](https://gitter.im/gbrlsnchs/jwt?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

## About
Expand All @@ -14,46 +14,47 @@ Although there are many JWT packages out there for Go, many lack support for som

Support for [JWE](https://tools.ietf.org/html/rfc7516) isn't provided. Instead, [JWS](https://tools.ietf.org/html/rfc7515) is used, narrowed down to the [JWT specification](https://tools.ietf.org/html/rfc7519).

### Supported signing methods
| | SHA-256 | SHA-384 | SHA-512 |
|:-------:|:------------------:|:------------------:|:------------------:|
| HMAC | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| RSA | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| RSA-PSS | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| ECDSA | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| EdDSA | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_check_mark: |

## Usage
Full documentation [here](https://godoc.org/github.com/gbrlsnchs/jwt).

### Installing
#### Go 1.10
`vgo get -u github.com/gbrlsnchs/jwt/v2`
#### Go 1.11 or after
`go get -u github.com/gbrlsnchs/jwt/v2`
`go get -u github.com/gbrlsnchs/jwt/v3`

### Importing
```go
import (
// ...

"github.com/gbrlsnchs/jwt/v2"
"github.com/gbrlsnchs/jwt/v3"
)
```

### Signing a simple JWT
```go
// Timestamp the beginning.
now := time.Now()
// Define a signer.
hs256 := jwt.NewHS256("secret")
hs256 := jwt.NewHMAC(jwt.SHA256, []byte("secret"))
jot := &jwt.JWT{
Issuer: "gbrlsnchs",
Subject: "someone",
Audience: "gophers",
ExpirationTime: now.Add(24 * 30 * 12 * time.Hour).Unix(),
NotBefore: now.Add(30 * time.Minute).Unix(),
IssuedAt: now.Unix(),
ID: "foobar",
}
jot.SetAlgorithm(hs256)
jot.SetKeyID("kid")
payload, err := jwt.Marshal(jot)
if err != nil {
// handle error
Header: jwt.Header{KeyID: "kid"},
Claims: &jwt.Claims{
Issuer: "gbrlsnchs",
Subject: "someone",
Audience: jwt.Audience{"gophers"},
ExpirationTime: now.Add(24 * 30 * 12 * time.Hour).Unix(),
NotBefore: now.Add(30 * time.Minute).Unix(),
IssuedAt: now.Unix(),
ID: "foobar",
},
}
token, err := hs256.Sign(payload)
token, err := jwt.Sign(jot, hs256)
if err != nil {
// handle error
}
Expand All @@ -64,38 +65,33 @@ log.Printf("token = %s", token)
#### First, create a custom type and embed a JWT pointer in it
```go
type Token struct {
*jwt.JWT
jwt.JWT
IsLoggedIn bool `json:"isLoggedIn"`
CustomField string `json:"customField,omitempty"`
}
```

#### Now initialize, marshal and sign it
```go
// Timestamp the beginning.
now := time.Now()
// Define a signer.
hs256 := jwt.NewHS256("secret")
hs256 := jwt.NewHMAC(jwt.SHA256, []byte("secret"))
jot := &Token{
JWT: &jwt.JWT{
Issuer: "gbrlsnchs",
Subject: "someone",
Audience: "gophers",
ExpirationTime: now.Add(24 * 30 * 12 * time.Hour).Unix(),
NotBefore: now.Add(30 * time.Minute).Unix(),
IssuedAt: now.Unix(),
ID: "foobar",
JWT: jwt.JWT{
Header: jwt.Header{KeyID: "kid"},
Claims: &jwt.Claims{
Issuer: "gbrlsnchs",
Subject: "someone",
Audience: jwt.Audience{"gophers"},
ExpirationTime: now.Add(24 * 30 * 12 * time.Hour).Unix(),
NotBefore: now.Add(30 * time.Minute).Unix(),
IssuedAt: now.Unix(),
ID: "foobar",
},
},
IsLoggedIn: true,
CustomField: "myCustomField",
}
jot.SetAlgorithm(hs256)
jot.SetKeyID("kid")
payload, err := jwt.Marshal(jot)
if err != nil {
// handle error
}
token, err := hs256.Sign(payload)
token, err := jwt.Sign(jot, hs256)
if err != nil {
// handle error
}
Expand All @@ -104,35 +100,25 @@ log.Printf("token = %s", token)

### Verifying and validating a JWT
```go
// Timestamp the beginning.
now := time.Now()
// Define a signer.
hs256 := jwt.NewHS256("secret")
// This is a mocked token for demonstration purposes only.
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." +
hs256 := jwt.NewHMAC(jwt.SHA256, []byte("secret"))
token := []byte("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." +
"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ." +
"lZ1zDoGNAv3u-OclJtnoQKejE8_viHlMtGlAxE8AE0Q"
"lZ1zDoGNAv3u-OclJtnoQKejE8_viHlMtGlAxE8AE0Q")

// First, extract the payload and signature.
// This enables unmarshaling the JWT first and
// verifying it later or vice versa.
payload, sig, err := jwt.Parse(token)
raw, err := jwt.Verify(token, hs256)
if err != nil {
// handle error
}
if err = hs256.Verify(payload, sig); err != nil {
// handle error
}
var jot Token
if err = jwt.Unmarshal(payload, &jot); err != nil {
if err = raw.Decode(&jot); err != nil {
// handle error
}

// Validate fields.
iatValidator := jwt.IssuedAtValidator(now)
expValidator := jwt.ExpirationTimeValidator(now)
audValidator := jwt.AudienceValidator("admin")
if err = jot.Validate(iatValidator, expValidator, audValidator); err != nil {
if err := jot.Validate(iatValidator, expValidator, audValidator); err != nil {
switch err {
case jwt.ErrIatValidation:
// handle "iat" validation error
Expand Down
44 changes: 44 additions & 0 deletions audience.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package jwt

import "encoding/json"

// Audience is a special claim that may either be
// a single string or an array of strings, as per the RFC 7519.
type Audience []string

// MarshalJSON implements a marshaling function for "aud" claim.
func (a Audience) MarshalJSON() ([]byte, error) {
switch len(a) {
case 0:
return json.Marshal("") // nil or empty slice returns an empty string
case 1:
return json.Marshal(a[0])
default:
return json.Marshal([]string(a))
}
}

// UnmarshalJSON implements an unmarshaling function for "aud" claim.
// TODO(gbrlsnchs): create tests for this method.
func (a *Audience) UnmarshalJSON(b []byte) error {
var (
v interface{}
err error
)
if err = json.Unmarshal(b, &v); err != nil {
return err
}
switch vv := v.(type) {
case string:
aud := make(Audience, 1)
aud[0] = vv
*a = aud
case []interface{}:
aud := make(Audience, 0, len(vv))
for i := range vv {
aud[i] = vv[i].(string)
}
*a = aud
}
return nil
}
45 changes: 45 additions & 0 deletions audience_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package jwt_test

import (
"encoding/json"
"testing"

. "github.com/gbrlsnchs/jwt/v3"
)

func TestAudienceMarshal(t *testing.T) {
testCases := []struct {
aud Audience
expected string
}{
{Audience{"foo"}, `"foo"`},
{Audience{"foo", "bar"}, `["foo","bar"]`},
{nil, `""`},
{Audience{}, `""`},
{Audience{""}, `""`},
}
for _, tc := range testCases {
t.Run(tc.expected, func(t *testing.T) {
b, err := json.Marshal(tc.aud)
if err != nil {
t.Fatal(err)
}
if want, got := tc.expected, b; want != string(got) {
t.Errorf("want %s, got %s", want, got)
}
})
}
}

func TestAudienceOmitempty(t *testing.T) {
v := struct {
Audience Audience `json:"aud,omitempty"`
}{}
b, err := json.Marshal(v)
if err != nil {
t.Fatal(err)
}
if want, got := "{}", b; want != string(got) {
t.Errorf("want %s, got %s", want, got)
}
}
44 changes: 20 additions & 24 deletions benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,59 +4,55 @@ import (
"testing"
"time"

. "github.com/gbrlsnchs/jwt/v2"
. "github.com/gbrlsnchs/jwt/v3"
)

const benchMock = "eyJhbGciOiJIUzI1NiIsImtpZCI6ImtpZCIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MzU5NDE1NjcsImV4cCI6MTU2NzA0NTU2NywibmJmIjoxNTM1OTQzMzY3LCJqdGkiOiJCZW5jaG1hcmtTaWduIiwiYXVkIjoiYmVuY2htYXJrIiwic3ViIjoibWUiLCJpc3MiOiJnYnJsc25jaHMiLCJuYW1lIjoiZm9vYmFyIiwiaXNCZW5jaCI6dHJ1ZX0.bJSm0om7-BRCuLbICllYEAH7YsAT1cW2fdSfKcMnhOg"
var benchMock = []byte("eyJhbGciOiJIUzI1NiIsImtpZCI6ImtpZCIsInR5cCI6IkpXVCJ9." +
"eyJpYXQiOjE1MzU5NDE1NjcsImV4cCI6MTU2NzA0NTU2NywibmJmIjoxNTM1OTQzMzY3LCJqdGkiOiJCZW5jaG1hcmtTaWduIiwiYXVkIjoiYmVuY2htYXJrIiwic3ViIjoibWUiLCJpc3MiOiJnYnJsc25jaHMiLCJuYW1lIjoiZm9vYmFyIiwiaXNCZW5jaCI6dHJ1ZX0." +
"bJSm0om7-BRCuLbICllYEAH7YsAT1cW2fdSfKcMnhOg")

var benchSigner = NewHS256("benchmark")
var hs256 = NewHMAC(SHA256, []byte("benchmark"))

type benchToken struct {
*JWT
JWT
Name string `json:"name,omitempty"`
IsBench bool `json:"isBench"`
}

func BenchmarkSign(b *testing.B) {
now := time.Now()
jot := &benchToken{
JWT: &JWT{
Issuer: "gbrlsnchs",
Subject: "me",
Audience: "benchmark",
ExpirationTime: now.Add(24 * 30 * 12 * time.Hour).Unix(),
NotBefore: now.Add(30 * time.Minute).Unix(),
IssuedAt: now.Unix(),
ID: b.Name(),
JWT: JWT{
Header: Header{KeyID: "kid"},
Claims: &Claims{
Issuer: "gbrlsnchs",
Subject: "me",
Audience: Audience{"benchmark"},
ExpirationTime: now.Add(24 * 30 * 12 * time.Hour).Unix(),
NotBefore: now.Add(30 * time.Minute).Unix(),
IssuedAt: now.Unix(),
ID: b.Name(),
},
},
Name: "foobar",
IsBench: true,
}
jot.SetAlgorithm(benchSigner)
jot.SetKeyID("kid")
b.ResetTimer()
for i := 0; i < b.N; i++ {
payload, err := Marshal(jot)
if err != nil {
b.Fatal(err)
}
if _, err = benchSigner.Sign(payload); err != nil {
if _, err := Sign(jot, hs256); err != nil {
b.Fatal(err)
}
}
}

func BenchmarkVerify(b *testing.B) {
for i := 0; i < b.N; i++ {
payload, sig, err := Parse(benchMock)
raw, err := Verify(benchMock, hs256)
if err != nil {
b.Fatal(err)
}
var jot benchToken
if err = Unmarshal(payload, &jot); err != nil {
b.Fatal(err)
}
if err = benchSigner.Verify(payload, sig); err != nil {
if err = raw.Decode(&jot); err != nil {
b.Fatal(err)
}
}
Expand Down
Loading

0 comments on commit c67b2ee

Please sign in to comment.