Replace dgrijalva/jwt-go with thermokarst/jwt

This commit is contained in:
Matthew Dillon 2015-04-22 15:08:59 -08:00
parent c7f534271e
commit 77cb7828cf
30 changed files with 824 additions and 1528 deletions

9
Godeps/Godeps.json generated
View file

@ -11,11 +11,6 @@
"Comment": "1.2.0-93-g2bcd11f",
"Rev": "2bcd11f863d540a1b190dc03b6c4f634c6ae91d4"
},
{
"ImportPath": "github.com/dgrijalva/jwt-go",
"Comment": "v2.2.0-15-g61124b6",
"Rev": "61124b62ad244d655f87d944aefaa2ae5a0d2f16"
},
{
"ImportPath": "github.com/gorilla/context",
"Rev": "215affda49addc4c8ef7e2534915df2c8c35c6cd"
@ -42,6 +37,10 @@
"Comment": "go1.0-cutoff-29-g30ed220",
"Rev": "30ed2200d7ec99cf17272292f1d4b7b0bd7165db"
},
{
"ImportPath": "github.com/thermokarst/jwt",
"Rev": "774185ba9ebde2a02689173b736b760a13a6b777"
},
{
"ImportPath": "golang.org/x/crypto/bcrypt",
"Rev": "1351f936d976c60a0a48d728281922cf63eafb8d"

View file

@ -1,4 +0,0 @@
.DS_Store
bin

View file

@ -1,8 +0,0 @@
Copyright (c) 2012 Dave Grijalva
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,55 +0,0 @@
A [go](http://www.golang.org) (or 'golang' for search engine friendliness) implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-jones-json-web-token.html)
**NOTICE:** We recently introduced a breaking change in the API. Please refer to [VERSION_HISTORY.md](VERSION_HISTORY.md) for details.
## What the heck is a JWT?
In short, it's a signed JSON object that does something useful (for example, authentication). It's commonly used for `Bearer` tokens in Oauth 2. A token is made of three parts, separated by `.`'s. The first two parts are JSON objects, that have been [base64url](http://tools.ietf.org/html/rfc4648) encoded. The last part is the signature, encoded the same way.
The first part is called the header. It contains the necessary information for verifying the last part, the signature. For example, which encryption method was used for signing and what key was used.
The part in the middle is the interesting bit. It's called the Claims and contains the actual stuff you care about. Refer to [the RFC](http://self-issued.info/docs/draft-jones-json-web-token.html) for information about reserved keys and the proper way to add your own.
## What's in the box?
This library supports the parsing and verification as well as the generation and signing of JWTs. Current supported signing algorithms are RSA256 and HMAC SHA256, though hooks are present for adding your own.
## Parse and Verify
Parsing and verifying tokens is pretty straight forward. You pass in the token and a function for looking up the key. This is done as a callback since you may need to parse the token to find out what signing method and key was used.
```go
token, err := jwt.Parse(myToken, func(token *jwt.Token) (interface{}, error) {
return myLookupKey(token.Header["kid"])
})
if err == nil && token.Valid {
deliverGoodness("!")
} else {
deliverUtterRejection(":(")
}
```
## Create a token
```go
// Create the token
token := jwt.New(jwt.SigningMethodHS256)
// Set some claims
token.Claims["foo"] = "bar"
token.Claims["exp"] = time.Now().Add(time.Hour * 72).Unix()
// Sign and get the complete encoded token as a string
tokenString, err := token.SignedString(mySigningKey)
```
## Project Status & Versioning
This library is considered production ready. Feedback and feature requests are appreciated. The API should be considered stable. There should be very few backwards-incompatible changes outside of major version updates (and only with good reason).
This project uses [Semantic Versioning 2.0.0](http://semver.org). Accepted pull requests will land on `master`. Periodically, versions will be tagged from `master`. You can find all the releases on [the project releases page](https://github.com/dgrijalva/jwt-go/releases).
## More
Documentation can be found [on godoc.org](http://godoc.org/github.com/dgrijalva/jwt-go).
The command line utility included in this project (cmd/jwt) provides a straightforward example of token creation and parsing as well as a useful tool for debugging your own integration. For a more http centric example, see [this gist](https://gist.github.com/cryptix/45c33ecf0ae54828e63b).

View file

@ -1,54 +0,0 @@
## `jwt-go` Version History
#### 2.2.0
* Gracefully handle a `nil` `Keyfunc` being passed to `Parse`. Result will now be the parsed token and an error, instead of a panic.
#### 2.1.0
Backwards compatible API change that was missed in 2.0.0.
* The `SignedString` method on `Token` now takes `interface{}` instead of `[]byte`
#### 2.0.0
There were two major reasons for breaking backwards compatibility with this update. The first was a refactor required to expand the width of the RSA and HMAC-SHA signing implementations. There will likely be no required code changes to support this change.
The second update, while unfortunately requiring a small change in integration, is required to open up this library to other signing methods. Not all keys used for all signing methods have a single standard on-disk representation. Requiring `[]byte` as the type for all keys proved too limiting. Additionally, this implementation allows for pre-parsed tokens to be reused, which might matter in an application that parses a high volume of tokens with a small set of keys. Backwards compatibilty has been maintained for passing `[]byte` to the RSA signing methods, but they will also accept `*rsa.PublicKey` and `*rsa.PrivateKey`.
It is likely the only integration change required here will be to change `func(t *jwt.Token) ([]byte, error)` to `func(t *jwt.Token) (interface{}, error)` when calling `Parse`.
* **Compatibility Breaking Changes**
* `SigningMethodHS256` is now `*SigningMethodHMAC` instead of `type struct`
* `SigningMethodRS256` is now `*SigningMethodRSA` instead of `type struct`
* `KeyFunc` now returns `interface{}` instead of `[]byte`
* `SigningMethod.Sign` now takes `interface{}` instead of `[]byte` for the key
* `SigningMethod.Verify` now takes `interface{}` instead of `[]byte` for the key
* Renamed type `SigningMethodHS256` to `SigningMethodHMAC`. Specific sizes are now just instances of this type.
* Added public package global `SigningMethodHS256`
* Added public package global `SigningMethodHS384`
* Added public package global `SigningMethodHS512`
* Renamed type `SigningMethodRS256` to `SigningMethodRSA`. Specific sizes are now just instances of this type.
* Added public package global `SigningMethodRS256`
* Added public package global `SigningMethodRS384`
* Added public package global `SigningMethodRS512`
* Moved sample private key for HMAC tests from an inline value to a file on disk. Value is unchanged.
* Refactored the RSA implementation to be easier to read
* Exposed helper methods `ParseRSAPrivateKeyFromPEM` and `ParseRSAPublicKeyFromPEM`
#### 1.0.2
* Fixed bug in parsing public keys from certificates
* Added more tests around the parsing of keys for RS256
* Code refactoring in RS256 implementation. No functional changes
#### 1.0.1
* Fixed panic if RS256 signing method was passed an invalid key
#### 1.0.0
* First versioned release
* API stabilized
* Supports creating, signing, parsing, and validating JWT tokens
* Supports RS256 and HS256 signing methods

View file

@ -1,186 +0,0 @@
// A useful example app. You can use this to debug your tokens on the command line.
// This is also a great place to look at how you might use this library.
//
// Example usage:
// The following will create and sign a token, then verify it and output the original claims.
// echo {\"foo\":\"bar\"} | bin/jwt -key test/sample_key -alg RS256 -sign - | bin/jwt -key test/sample_key.pub -verify -
package main
import (
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"regexp"
"github.com/dgrijalva/jwt-go"
)
var (
// Options
flagAlg = flag.String("alg", "", "signing algorithm identifier")
flagKey = flag.String("key", "", "path to key file or '-' to read from stdin")
flagCompact = flag.Bool("compact", false, "output compact JSON")
flagDebug = flag.Bool("debug", false, "print out all kinds of debug data")
// Modes - exactly one of these is required
flagSign = flag.String("sign", "", "path to claims object to sign or '-' to read from stdin")
flagVerify = flag.String("verify", "", "path to JWT token to verify or '-' to read from stdin")
)
func main() {
// Usage message if you ask for -help or if you mess up inputs.
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
fmt.Fprintf(os.Stderr, " One of the following flags is required: sign, verify\n")
flag.PrintDefaults()
}
// Parse command line options
flag.Parse()
// Do the thing. If something goes wrong, print error to stderr
// and exit with a non-zero status code
if err := start(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
// Figure out which thing to do and then do that
func start() error {
if *flagSign != "" {
return signToken()
} else if *flagVerify != "" {
return verifyToken()
} else {
flag.Usage()
return fmt.Errorf("None of the required flags are present. What do you want me to do?")
}
}
// Helper func: Read input from specified file or stdin
func loadData(p string) ([]byte, error) {
if p == "" {
return nil, fmt.Errorf("No path specified")
}
var rdr io.Reader
if p == "-" {
rdr = os.Stdin
} else {
if f, err := os.Open(p); err == nil {
rdr = f
defer f.Close()
} else {
return nil, err
}
}
return ioutil.ReadAll(rdr)
}
// Print a json object in accordance with the prophecy (or the command line options)
func printJSON(j interface{}) error {
var out []byte
var err error
if *flagCompact == false {
out, err = json.MarshalIndent(j, "", " ")
} else {
out, err = json.Marshal(j)
}
if err == nil {
fmt.Println(string(out))
}
return err
}
// Verify a token and output the claims. This is a great example
// of how to verify and view a token.
func verifyToken() error {
// get the token
tokData, err := loadData(*flagVerify)
if err != nil {
return fmt.Errorf("Couldn't read token: %v", err)
}
// trim possible whitespace from token
tokData = regexp.MustCompile(`\s*$`).ReplaceAll(tokData, []byte{})
if *flagDebug {
fmt.Fprintf(os.Stderr, "Token len: %v bytes\n", len(tokData))
}
// Parse the token. Load the key from command line option
token, err := jwt.Parse(string(tokData), func(t *jwt.Token) (interface{}, error) {
return loadData(*flagKey)
})
// Print some debug data
if *flagDebug && token != nil {
fmt.Fprintf(os.Stderr, "Header:\n%v\n", token.Header)
fmt.Fprintf(os.Stderr, "Claims:\n%v\n", token.Claims)
}
// Print an error if we can't parse for some reason
if err != nil {
return fmt.Errorf("Couldn't parse token: %v", err)
}
// Is token invalid?
if !token.Valid {
return fmt.Errorf("Token is invalid")
}
// Print the token details
if err := printJSON(token.Claims); err != nil {
return fmt.Errorf("Failed to output claims: %v", err)
}
return nil
}
// Create, sign, and output a token. This is a great, simple example of
// how to use this library to create and sign a token.
func signToken() error {
// get the token data from command line arguments
tokData, err := loadData(*flagSign)
if err != nil {
return fmt.Errorf("Couldn't read token: %v", err)
} else if *flagDebug {
fmt.Fprintf(os.Stderr, "Token: %v bytes", len(tokData))
}
// parse the JSON of the claims
var claims map[string]interface{}
if err := json.Unmarshal(tokData, &claims); err != nil {
return fmt.Errorf("Couldn't parse claims JSON: %v", err)
}
// get the key
keyData, err := loadData(*flagKey)
if err != nil {
return fmt.Errorf("Couldn't read key: %v", err)
}
// get the signing alg
alg := jwt.GetSigningMethod(*flagAlg)
if alg == nil {
return fmt.Errorf("Couldn't find signing method: %v", *flagAlg)
}
// create a new token
token := jwt.New(alg)
token.Claims = claims
if out, err := token.SignedString(keyData); err == nil {
fmt.Println(out)
} else {
return fmt.Errorf("Error signing token: %v", err)
}
return nil
}

View file

@ -1,4 +0,0 @@
// Package jwt is a Go implementation of JSON Web Tokens: http://self-issued.info/docs/draft-jones-json-web-token.html
//
// See README.md for more info.
package jwt

View file

@ -1,52 +0,0 @@
package jwt_test
import (
"fmt"
"github.com/dgrijalva/jwt-go"
"time"
)
func ExampleParse(myToken string, myLookupKey func(interface{}) (interface{}, error)) {
token, err := jwt.Parse(myToken, func(token *jwt.Token) (interface{}, error) {
return myLookupKey(token.Header["kid"])
})
if err == nil && token.Valid {
fmt.Println("Your token is valid. I like your style.")
} else {
fmt.Println("This token is terrible! I cannot accept this.")
}
}
func ExampleNew(mySigningKey []byte) (string, error) {
// Create the token
token := jwt.New(jwt.SigningMethodHS256)
// Set some claims
token.Claims["foo"] = "bar"
token.Claims["exp"] = time.Now().Add(time.Hour * 72).Unix()
// Sign and get the complete encoded token as a string
tokenString, err := token.SignedString(mySigningKey)
return tokenString, err
}
func ExampleParse_errorChecking(myToken string, myLookupKey func(interface{}) (interface{}, error)) {
token, err := jwt.Parse(myToken, func(token *jwt.Token) (interface{}, error) {
return myLookupKey(token.Header["kid"])
})
if token.Valid {
fmt.Println("You look nice today")
} else if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
fmt.Println("That's not even a token")
} else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
// Token is either expired or not active yet
fmt.Println("Timing is everything")
} else {
fmt.Println("Couldn't handle this token:", err)
}
} else {
fmt.Println("Couldn't handle this token:", err)
}
}

View file

@ -1,84 +0,0 @@
package jwt
import (
"crypto"
"crypto/hmac"
"errors"
)
// Implements the HMAC-SHA family of signing methods signing methods
type SigningMethodHMAC struct {
Name string
Hash crypto.Hash
}
// Specific instances for HS256 and company
var (
SigningMethodHS256 *SigningMethodHMAC
SigningMethodHS384 *SigningMethodHMAC
SigningMethodHS512 *SigningMethodHMAC
ErrSignatureInvalid = errors.New("signature is invalid")
)
func init() {
// HS256
SigningMethodHS256 = &SigningMethodHMAC{"HS256", crypto.SHA256}
RegisterSigningMethod(SigningMethodHS256.Alg(), func() SigningMethod {
return SigningMethodHS256
})
// HS384
SigningMethodHS384 = &SigningMethodHMAC{"HS384", crypto.SHA384}
RegisterSigningMethod(SigningMethodHS384.Alg(), func() SigningMethod {
return SigningMethodHS384
})
// HS512
SigningMethodHS512 = &SigningMethodHMAC{"HS512", crypto.SHA512}
RegisterSigningMethod(SigningMethodHS512.Alg(), func() SigningMethod {
return SigningMethodHS512
})
}
func (m *SigningMethodHMAC) Alg() string {
return m.Name
}
func (m *SigningMethodHMAC) Verify(signingString, signature string, key interface{}) error {
if keyBytes, ok := key.([]byte); ok {
var sig []byte
var err error
if sig, err = DecodeSegment(signature); err == nil {
if !m.Hash.Available() {
return ErrHashUnavailable
}
hasher := hmac.New(m.Hash.New, keyBytes)
hasher.Write([]byte(signingString))
if !hmac.Equal(sig, hasher.Sum(nil)) {
err = ErrSignatureInvalid
}
}
return err
}
return ErrInvalidKey
}
// Implements the Sign method from SigningMethod for this signing method.
// Key must be []byte
func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) (string, error) {
if keyBytes, ok := key.([]byte); ok {
if !m.Hash.Available() {
return "", ErrHashUnavailable
}
hasher := hmac.New(m.Hash.New, keyBytes)
hasher.Write([]byte(signingString))
return EncodeSegment(hasher.Sum(nil)), nil
}
return "", ErrInvalidKey
}

View file

@ -1,79 +0,0 @@
package jwt_test
import (
"github.com/dgrijalva/jwt-go"
"io/ioutil"
"strings"
"testing"
)
var hmacTestData = []struct {
name string
tokenString string
alg string
claims map[string]interface{}
valid bool
}{
{
"web sample",
"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
"HS256",
map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true},
true,
},
{
"HS384",
"eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJleHAiOjEuMzAwODE5MzhlKzA5LCJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiam9lIn0.KWZEuOD5lbBxZ34g7F-SlVLAQ_r5KApWNWlZIIMyQVz5Zs58a7XdNzj5_0EcNoOy",
"HS384",
map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true},
true,
},
{
"HS512",
"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOjEuMzAwODE5MzhlKzA5LCJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiam9lIn0.CN7YijRX6Aw1n2jyI2Id1w90ja-DEMYiWixhYCyHnrZ1VfJRaFQz1bEbjjA5Fn4CLYaUG432dEYmSbS4Saokmw",
"HS512",
map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true},
true,
},
{
"web sample: invalid",
"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXo",
"HS256",
map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true},
false,
},
}
// Sample data from http://tools.ietf.org/html/draft-jones-json-web-signature-04#appendix-A.1
var hmacTestKey, _ = ioutil.ReadFile("test/hmacTestKey")
func TestHMACVerify(t *testing.T) {
for _, data := range hmacTestData {
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
err := method.Verify(strings.Join(parts[0:2], "."), parts[2], hmacTestKey)
if data.valid && err != nil {
t.Errorf("[%v] Error while verifying key: %v", data.name, err)
}
if !data.valid && err == nil {
t.Errorf("[%v] Invalid key passed validation", data.name)
}
}
}
func TestHMACSign(t *testing.T) {
for _, data := range hmacTestData {
if data.valid {
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
sig, err := method.Sign(strings.Join(parts[0:2], "."), hmacTestKey)
if err != nil {
t.Errorf("[%v] Error signing token: %v", data.name, err)
}
if sig != parts[2] {
t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", data.name, sig, parts[2])
}
}
}
}

View file

@ -1,237 +0,0 @@
package jwt
import (
"encoding/base64"
"encoding/json"
"errors"
"net/http"
"strings"
"time"
)
// TimeFunc provides the current time when parsing token to validate "exp" claim (expiration time).
// You can override it to use another time value. This is useful for testing or if your
// server uses a different time zone than your tokens.
var TimeFunc = time.Now
// Parse methods use this callback function to supply
// the key for verification. The function receives the parsed,
// but unverified Token. This allows you to use propries in the
// Header of the token (such as `kid`) to identify which key to use.
type Keyfunc func(*Token) (interface{}, error)
// Error constants
var (
ErrInvalidKey = errors.New("key is invalid or of invalid type")
ErrHashUnavailable = errors.New("the requested hash function is unavailable")
ErrNoTokenInRequest = errors.New("no token present in request")
)
// A JWT Token. Different fields will be used depending on whether you're
// creating or parsing/verifying a token.
type Token struct {
Raw string // The raw token. Populated when you Parse a token
Method SigningMethod // The signing method used or to be used
Header map[string]interface{} // The first segment of the token
Claims map[string]interface{} // The second segment of the token
Signature string // The third segment of the token. Populated when you Parse a token
Valid bool // Is the token valid? Populated when you Parse/Verify a token
}
// Create a new Token. Takes a signing method
func New(method SigningMethod) *Token {
return &Token{
Header: map[string]interface{}{
"typ": "JWT",
"alg": method.Alg(),
},
Claims: make(map[string]interface{}),
Method: method,
}
}
// Get the complete, signed token
func (t *Token) SignedString(key interface{}) (string, error) {
var sig, sstr string
var err error
if sstr, err = t.SigningString(); err != nil {
return "", err
}
if sig, err = t.Method.Sign(sstr, key); err != nil {
return "", err
}
return strings.Join([]string{sstr, sig}, "."), nil
}
// 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
}
// Parse, validate, and return a token.
// keyFunc will receive the parsed token and should return the key for validating.
// If everything is kosher, err will be nil
func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
parts := strings.Split(tokenString, ".")
if len(parts) != 3 {
return nil, &ValidationError{err: "token contains an invalid number of segments", Errors: ValidationErrorMalformed}
}
var err error
token := &Token{Raw: tokenString}
// parse Header
var headerBytes []byte
if headerBytes, err = DecodeSegment(parts[0]); err != nil {
return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed}
}
if err = json.Unmarshal(headerBytes, &token.Header); err != nil {
return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed}
}
// parse Claims
var claimBytes []byte
if claimBytes, err = DecodeSegment(parts[1]); err != nil {
return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed}
}
if err = json.Unmarshal(claimBytes, &token.Claims); err != nil {
return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed}
}
// Lookup signature method
if method, ok := token.Header["alg"].(string); ok {
if token.Method = GetSigningMethod(method); token.Method == nil {
return token, &ValidationError{err: "signing method (alg) is unavailable.", Errors: ValidationErrorUnverifiable}
}
} else {
return token, &ValidationError{err: "signing method (alg) is unspecified.", Errors: ValidationErrorUnverifiable}
}
// Lookup key
var key interface{}
if keyFunc == nil {
// keyFunc was not provided. short circuiting validation
return token, &ValidationError{err: "no Keyfunc was provided.", Errors: ValidationErrorUnverifiable}
}
if key, err = keyFunc(token); err != nil {
// keyFunc returned an error
return token, &ValidationError{err: err.Error(), Errors: ValidationErrorUnverifiable}
}
// Check expiration times
vErr := &ValidationError{}
now := TimeFunc().Unix()
if exp, ok := token.Claims["exp"].(float64); ok {
if now > int64(exp) {
vErr.err = "token is expired"
vErr.Errors |= ValidationErrorExpired
}
}
if nbf, ok := token.Claims["nbf"].(float64); ok {
if now < int64(nbf) {
vErr.err = "token is not valid yet"
vErr.Errors |= ValidationErrorNotValidYet
}
}
// Perform validation
if err = token.Method.Verify(strings.Join(parts[0:2], "."), parts[2], key); err != nil {
vErr.err = err.Error()
vErr.Errors |= ValidationErrorSignatureInvalid
}
if vErr.valid() {
token.Valid = true
return token, nil
}
return token, vErr
}
// The errors that might occur when parsing and validating a token
const (
ValidationErrorMalformed uint32 = 1 << iota // Token is malformed
ValidationErrorUnverifiable // Token could not be verified because of signing problems
ValidationErrorSignatureInvalid // Signature validation failed
ValidationErrorExpired // Exp validation failed
ValidationErrorNotValidYet // NBF validation failed
)
// The error from Parse if token is not valid
type ValidationError struct {
err string
Errors uint32 // bitfield. see ValidationError... constants
}
// Validation error is an error type
func (e ValidationError) Error() string {
if e.err == "" {
return "token is invalid"
}
return e.err
}
// No errors
func (e *ValidationError) valid() bool {
if e.Errors > 0 {
return false
}
return true
}
// Try to find the token in an http.Request.
// This method will call ParseMultipartForm if there's no token in the header.
// Currently, it looks in the Authorization header as well as
// looking for an 'access_token' request parameter in req.Form.
func ParseFromRequest(req *http.Request, keyFunc Keyfunc) (token *Token, err error) {
// Look for an Authorization header
if ah := req.Header.Get("Authorization"); ah != "" {
// Should be a bearer token
if len(ah) > 6 && strings.ToUpper(ah[0:6]) == "BEARER" {
return Parse(ah[7:], keyFunc)
}
}
// Look for "access_token" parameter
req.ParseMultipartForm(10e6)
if tokStr := req.Form.Get("access_token"); tokStr != "" {
return Parse(tokStr, keyFunc)
}
return nil, ErrNoTokenInRequest
}
// Encode JWT specific base64url encoding with padding stripped
func EncodeSegment(seg []byte) string {
return strings.TrimRight(base64.URLEncoding.EncodeToString(seg), "=")
}
// Decode JWT specific base64url encoding with padding stripped
func DecodeSegment(seg string) ([]byte, error) {
if l := len(seg) % 4; l > 0 {
seg += strings.Repeat("=", 4-l)
}
return base64.URLEncoding.DecodeString(seg)
}

View file

@ -1,174 +0,0 @@
package jwt_test
import (
"fmt"
"github.com/dgrijalva/jwt-go"
"io/ioutil"
"net/http"
"reflect"
"testing"
"time"
)
var (
jwtTestDefaultKey []byte
defaultKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return jwtTestDefaultKey, nil }
emptyKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, nil }
errorKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, fmt.Errorf("error loading key") }
nilKeyFunc jwt.Keyfunc = nil
)
var jwtTestData = []struct {
name string
tokenString string
keyfunc jwt.Keyfunc
claims map[string]interface{}
valid bool
errors uint32
}{
{
"basic",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
defaultKeyFunc,
map[string]interface{}{"foo": "bar"},
true,
0,
},
{
"basic expired",
"", // autogen
defaultKeyFunc,
map[string]interface{}{"foo": "bar", "exp": float64(time.Now().Unix() - 100)},
false,
jwt.ValidationErrorExpired,
},
{
"basic nbf",
"", // autogen
defaultKeyFunc,
map[string]interface{}{"foo": "bar", "nbf": float64(time.Now().Unix() + 100)},
false,
jwt.ValidationErrorNotValidYet,
},
{
"expired and nbf",
"", // autogen
defaultKeyFunc,
map[string]interface{}{"foo": "bar", "nbf": float64(time.Now().Unix() + 100), "exp": float64(time.Now().Unix() - 100)},
false,
jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired,
},
{
"basic invalid",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
defaultKeyFunc,
map[string]interface{}{"foo": "bar"},
false,
jwt.ValidationErrorSignatureInvalid,
},
{
"basic nokeyfunc",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
nilKeyFunc,
map[string]interface{}{"foo": "bar"},
false,
jwt.ValidationErrorUnverifiable,
},
{
"basic nokey",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
emptyKeyFunc,
map[string]interface{}{"foo": "bar"},
false,
jwt.ValidationErrorSignatureInvalid,
},
{
"basic errorkey",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
errorKeyFunc,
map[string]interface{}{"foo": "bar"},
false,
jwt.ValidationErrorUnverifiable,
},
}
func init() {
var e error
if jwtTestDefaultKey, e = ioutil.ReadFile("test/sample_key.pub"); e != nil {
panic(e)
}
}
func makeSample(c map[string]interface{}) string {
key, e := ioutil.ReadFile("test/sample_key")
if e != nil {
panic(e.Error())
}
token := jwt.New(jwt.SigningMethodRS256)
token.Claims = c
s, e := token.SignedString(key)
if e != nil {
panic(e.Error())
}
return s
}
func TestJWT(t *testing.T) {
for _, data := range jwtTestData {
if data.tokenString == "" {
data.tokenString = makeSample(data.claims)
}
token, err := jwt.Parse(data.tokenString, data.keyfunc)
if !reflect.DeepEqual(data.claims, token.Claims) {
t.Errorf("[%v] Claims mismatch. Expecting: %v Got: %v", data.name, data.claims, token.Claims)
}
if data.valid && err != nil {
t.Errorf("[%v] Error while verifying token: %T:%v", data.name, err, err)
}
if !data.valid && err == nil {
t.Errorf("[%v] Invalid token passed validation", data.name)
}
if data.errors != 0 {
if err == nil {
t.Errorf("[%v] Expecting error. Didn't get one.", data.name)
} else {
// compare the bitfield part of the error
if err.(*jwt.ValidationError).Errors != data.errors {
t.Errorf("[%v] Errors don't match expectation", data.name)
}
}
}
}
}
func TestParseRequest(t *testing.T) {
// Bearer token request
for _, data := range jwtTestData {
if data.tokenString == "" {
data.tokenString = makeSample(data.claims)
}
r, _ := http.NewRequest("GET", "/", nil)
r.Header.Set("Authorization", fmt.Sprintf("Bearer %v", data.tokenString))
token, err := jwt.ParseFromRequest(r, data.keyfunc)
if token == nil {
t.Errorf("[%v] Token was not found: %v", data.name, err)
continue
}
if !reflect.DeepEqual(data.claims, token.Claims) {
t.Errorf("[%v] Claims mismatch. Expecting: %v Got: %v", data.name, data.claims, token.Claims)
}
if data.valid && err != nil {
t.Errorf("[%v] Error while verifying token: %v", data.name, err)
}
if !data.valid && err == nil {
t.Errorf("[%v] Invalid token passed validation", data.name)
}
}
}

View file

@ -1,114 +0,0 @@
package jwt
import (
"crypto"
"crypto/rand"
"crypto/rsa"
)
// Implements the RSA family of signing methods signing methods
type SigningMethodRSA struct {
Name string
Hash crypto.Hash
}
// Specific instances for RS256 and company
var (
SigningMethodRS256 *SigningMethodRSA
SigningMethodRS384 *SigningMethodRSA
SigningMethodRS512 *SigningMethodRSA
)
func init() {
// RS256
SigningMethodRS256 = &SigningMethodRSA{"RS256", crypto.SHA256}
RegisterSigningMethod(SigningMethodRS256.Alg(), func() SigningMethod {
return SigningMethodRS256
})
// RS384
SigningMethodRS384 = &SigningMethodRSA{"RS384", crypto.SHA384}
RegisterSigningMethod(SigningMethodRS384.Alg(), func() SigningMethod {
return SigningMethodRS384
})
// RS512
SigningMethodRS512 = &SigningMethodRSA{"RS512", crypto.SHA512}
RegisterSigningMethod(SigningMethodRS512.Alg(), func() SigningMethod {
return SigningMethodRS512
})
}
func (m *SigningMethodRSA) Alg() string {
return m.Name
}
// Implements the Verify method from SigningMethod
// For this signing method, must be either a PEM encoded PKCS1 or PKCS8 RSA public key as
// []byte, or an rsa.PublicKey structure.
func (m *SigningMethodRSA) Verify(signingString, signature string, key interface{}) error {
var err error
// Decode the signature
var sig []byte
if sig, err = DecodeSegment(signature); err != nil {
return err
}
var rsaKey *rsa.PublicKey
switch k := key.(type) {
case []byte:
if rsaKey, err = ParseRSAPublicKeyFromPEM(k); err != nil {
return err
}
case *rsa.PublicKey:
rsaKey = k
default:
return ErrInvalidKey
}
// Create hasher
if !m.Hash.Available() {
return ErrHashUnavailable
}
hasher := m.Hash.New()
hasher.Write([]byte(signingString))
// Verify the signature
return rsa.VerifyPKCS1v15(rsaKey, m.Hash, hasher.Sum(nil), sig)
}
// Implements the Sign method from SigningMethod
// For this signing method, must be either a PEM encoded PKCS1 or PKCS8 RSA private key as
// []byte, or an rsa.PrivateKey structure.
func (m *SigningMethodRSA) Sign(signingString string, key interface{}) (string, error) {
var err error
var rsaKey *rsa.PrivateKey
switch k := key.(type) {
case []byte:
if rsaKey, err = ParseRSAPrivateKeyFromPEM(k); err != nil {
return "", err
}
case *rsa.PrivateKey:
rsaKey = k
default:
return "", ErrInvalidKey
}
// Create the hasher
if !m.Hash.Available() {
return "", ErrHashUnavailable
}
hasher := m.Hash.New()
hasher.Write([]byte(signingString))
// Sign the string and return the encoded bytes
if sigBytes, err := rsa.SignPKCS1v15(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil)); err == nil {
return EncodeSegment(sigBytes), nil
} else {
return "", err
}
}

View file

@ -1,144 +0,0 @@
package jwt_test
import (
"github.com/dgrijalva/jwt-go"
"io/ioutil"
"strings"
"testing"
)
var rsaTestData = []struct {
name string
tokenString string
alg string
claims map[string]interface{}
valid bool
}{
{
"Basic RS256",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
"RS256",
map[string]interface{}{"foo": "bar"},
true,
},
{
"Basic RS384",
"eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.W-jEzRfBigtCWsinvVVuldiuilzVdU5ty0MvpLaSaqK9PlAWWlDQ1VIQ_qSKzwL5IXaZkvZFJXT3yL3n7OUVu7zCNJzdwznbC8Z-b0z2lYvcklJYi2VOFRcGbJtXUqgjk2oGsiqUMUMOLP70TTefkpsgqDxbRh9CDUfpOJgW-dU7cmgaoswe3wjUAUi6B6G2YEaiuXC0XScQYSYVKIzgKXJV8Zw-7AN_DBUI4GkTpsvQ9fVVjZM9csQiEXhYekyrKu1nu_POpQonGd8yqkIyXPECNmmqH5jH4sFiF67XhD7_JpkvLziBpI-uh86evBUadmHhb9Otqw3uV3NTaXLzJw",
"RS384",
map[string]interface{}{"foo": "bar"},
true,
},
{
"Basic RS512",
"eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.zBlLlmRrUxx4SJPUbV37Q1joRcI9EW13grnKduK3wtYKmDXbgDpF1cZ6B-2Jsm5RB8REmMiLpGms-EjXhgnyh2TSHE-9W2gA_jvshegLWtwRVDX40ODSkTb7OVuaWgiy9y7llvcknFBTIg-FnVPVpXMmeV_pvwQyhaz1SSwSPrDyxEmksz1hq7YONXhXPpGaNbMMeDTNP_1oj8DZaqTIL9TwV8_1wb2Odt_Fy58Ke2RVFijsOLdnyEAjt2n9Mxihu9i3PhNBkkxa2GbnXBfq3kzvZ_xxGGopLdHhJjcGWXO-NiwI9_tiu14NRv4L2xC0ItD9Yz68v2ZIZEp_DuzwRQ",
"RS512",
map[string]interface{}{"foo": "bar"},
true,
},
{
"basic invalid: foo => bar",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
"RS256",
map[string]interface{}{"foo": "bar"},
false,
},
}
func TestRSAVerify(t *testing.T) {
key, _ := ioutil.ReadFile("test/sample_key.pub")
for _, data := range rsaTestData {
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
err := method.Verify(strings.Join(parts[0:2], "."), parts[2], key)
if data.valid && err != nil {
t.Errorf("[%v] Error while verifying key: %v", data.name, err)
}
if !data.valid && err == nil {
t.Errorf("[%v] Invalid key passed validation", data.name)
}
}
}
func TestRSASign(t *testing.T) {
key, _ := ioutil.ReadFile("test/sample_key")
for _, data := range rsaTestData {
if data.valid {
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
sig, err := method.Sign(strings.Join(parts[0:2], "."), key)
if err != nil {
t.Errorf("[%v] Error signing token: %v", data.name, err)
}
if sig != parts[2] {
t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", data.name, sig, parts[2])
}
}
}
}
func TestRSAVerifyWithPreParsedPrivateKey(t *testing.T) {
key, _ := ioutil.ReadFile("test/sample_key.pub")
parsedKey, err := jwt.ParseRSAPublicKeyFromPEM(key)
if err != nil {
t.Fatal(err)
}
testData := rsaTestData[0]
parts := strings.Split(testData.tokenString, ".")
err = jwt.SigningMethodRS256.Verify(strings.Join(parts[0:2], "."), parts[2], parsedKey)
if err != nil {
t.Errorf("[%v] Error while verifying key: %v", testData.name, err)
}
}
func TestRSAWithPreParsedPrivateKey(t *testing.T) {
key, _ := ioutil.ReadFile("test/sample_key")
parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key)
if err != nil {
t.Fatal(err)
}
testData := rsaTestData[0]
parts := strings.Split(testData.tokenString, ".")
sig, err := jwt.SigningMethodRS256.Sign(strings.Join(parts[0:2], "."), parsedKey)
if err != nil {
t.Errorf("[%v] Error signing token: %v", testData.name, err)
}
if sig != parts[2] {
t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", testData.name, sig, parts[2])
}
}
func TestRSAKeyParsing(t *testing.T) {
key, _ := ioutil.ReadFile("test/sample_key")
pubKey, _ := ioutil.ReadFile("test/sample_key.pub")
badKey := []byte("All your base are belong to key")
// Test parsePrivateKey
if _, e := jwt.ParseRSAPrivateKeyFromPEM(key); e != nil {
t.Errorf("Failed to parse valid private key: %v", e)
}
if k, e := jwt.ParseRSAPrivateKeyFromPEM(pubKey); e == nil {
t.Errorf("Parsed public key as valid private key: %v", k)
}
if k, e := jwt.ParseRSAPrivateKeyFromPEM(badKey); e == nil {
t.Errorf("Parsed invalid key as valid private key: %v", k)
}
// Test parsePublicKey
if _, e := jwt.ParseRSAPublicKeyFromPEM(pubKey); e != nil {
t.Errorf("Failed to parse valid public key: %v", e)
}
if k, e := jwt.ParseRSAPublicKeyFromPEM(key); e == nil {
t.Errorf("Parsed private key as valid public key: %v", k)
}
if k, e := jwt.ParseRSAPublicKeyFromPEM(badKey); e == nil {
t.Errorf("Parsed invalid key as valid private key: %v", k)
}
}

View file

@ -1,68 +0,0 @@
package jwt
import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
)
var (
ErrKeyMustBePEMEncoded = errors.New("Invalid Key: Key must be PEM encoded PKCS1 or PKCS8 private key")
ErrNotRSAPrivateKey = errors.New("Key is not a valid RSA private key")
)
// Parse PEM encoded PKCS1 or PKCS8 private key
func ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error) {
var err error
// Parse PEM block
var block *pem.Block
if block, _ = pem.Decode(key); block == nil {
return nil, ErrKeyMustBePEMEncoded
}
var parsedKey interface{}
if parsedKey, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil {
return nil, err
}
}
var pkey *rsa.PrivateKey
var ok bool
if pkey, ok = parsedKey.(*rsa.PrivateKey); !ok {
return nil, ErrNotRSAPrivateKey
}
return pkey, nil
}
// Parse PEM encoded PKCS1 or PKCS8 public key
func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) {
var err error
// Parse PEM block
var block *pem.Block
if block, _ = pem.Decode(key); block == nil {
return nil, ErrKeyMustBePEMEncoded
}
// Parse the key
var parsedKey interface{}
if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil {
if cert, err := x509.ParseCertificate(block.Bytes); err == nil {
parsedKey = cert.PublicKey
} else {
return nil, err
}
}
var pkey *rsa.PublicKey
var ok bool
if pkey, ok = parsedKey.(*rsa.PublicKey); !ok {
return nil, ErrNotRSAPrivateKey
}
return pkey, nil
}

View file

@ -1,24 +0,0 @@
package jwt
var signingMethods = map[string]func() SigningMethod{}
// Signing method
type SigningMethod interface {
Verify(signingString, signature string, key interface{}) error
Sign(signingString string, key interface{}) (string, error)
Alg() string
}
// Register the "alg" name and a factory function for signing method.
// This is typically done during init() in the method's implementation
func RegisterSigningMethod(alg string, f func() SigningMethod) {
signingMethods[alg] = f
}
// Get a signing method from an "alg" string
func GetSigningMethod(alg string) (method SigningMethod) {
if methodF, ok := signingMethods[alg]; ok {
method = methodF()
}
return
}

View file

@ -1 +0,0 @@
#5K+・シミew{ヲ住ウ(跼Tノ(ゥ┫メP.ソモ燾辻G<>感テwb="=.!r.Oタヘ奎gミ€

View file

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA4f5wg5l2hKsTeNem/V41fGnJm6gOdrj8ym3rFkEU/wT8RDtn
SgFEZOQpHEgQ7JL38xUfU0Y3g6aYw9QT0hJ7mCpz9Er5qLaMXJwZxzHzAahlfA0i
cqabvJOMvQtzD6uQv6wPEyZtDTWiQi9AXwBpHssPnpYGIn20ZZuNlX2BrClciHhC
PUIIZOQn/MmqTD31jSyjoQoV7MhhMTATKJx2XrHhR+1DcKJzQBSTAGnpYVaqpsAR
ap+nwRipr3nUTuxyGohBTSmjJ2usSeQXHI3bODIRe1AuTyHceAbewn8b462yEWKA
Rdpd9AjQW5SIVPfdsz5B6GlYQ5LdYKtznTuy7wIDAQABAoIBAQCwia1k7+2oZ2d3
n6agCAbqIE1QXfCmh41ZqJHbOY3oRQG3X1wpcGH4Gk+O+zDVTV2JszdcOt7E5dAy
MaomETAhRxB7hlIOnEN7WKm+dGNrKRvV0wDU5ReFMRHg31/Lnu8c+5BvGjZX+ky9
POIhFFYJqwCRlopGSUIxmVj5rSgtzk3iWOQXr+ah1bjEXvlxDOWkHN6YfpV5ThdE
KdBIPGEVqa63r9n2h+qazKrtiRqJqGnOrHzOECYbRFYhexsNFz7YT02xdfSHn7gM
IvabDDP/Qp0PjE1jdouiMaFHYnLBbgvlnZW9yuVf/rpXTUq/njxIXMmvmEyyvSDn
FcFikB8pAoGBAPF77hK4m3/rdGT7X8a/gwvZ2R121aBcdPwEaUhvj/36dx596zvY
mEOjrWfZhF083/nYWE2kVquj2wjs+otCLfifEEgXcVPTnEOPO9Zg3uNSL0nNQghj
FuD3iGLTUBCtM66oTe0jLSslHe8gLGEQqyMzHOzYxNqibxcOZIe8Qt0NAoGBAO+U
I5+XWjWEgDmvyC3TrOSf/KCGjtu0TSv30ipv27bDLMrpvPmD/5lpptTFwcxvVhCs
2b+chCjlghFSWFbBULBrfci2FtliClOVMYrlNBdUSJhf3aYSG2Doe6Bgt1n2CpNn
/iu37Y3NfemZBJA7hNl4dYe+f+uzM87cdQ214+jrAoGAXA0XxX8ll2+ToOLJsaNT
OvNB9h9Uc5qK5X5w+7G7O998BN2PC/MWp8H+2fVqpXgNENpNXttkRm1hk1dych86
EunfdPuqsX+as44oCyJGFHVBnWpm33eWQw9YqANRI+pCJzP08I5WK3osnPiwshd+
hR54yjgfYhBFNI7B95PmEQkCgYBzFSz7h1+s34Ycr8SvxsOBWxymG5zaCsUbPsL0
4aCgLScCHb9J+E86aVbbVFdglYa5Id7DPTL61ixhl7WZjujspeXZGSbmq0Kcnckb
mDgqkLECiOJW2NHP/j0McAkDLL4tysF8TLDO8gvuvzNC+WQ6drO2ThrypLVZQ+ry
eBIPmwKBgEZxhqa0gVvHQG/7Od69KWj4eJP28kq13RhKay8JOoN0vPmspXJo1HY3
CKuHRG+AP579dncdUnOMvfXOtkdM4vk0+hWASBQzM9xzVcztCa+koAugjVaLS9A+
9uQoqEeVNTckxx0S2bYevRy7hGQmUJTyQm3j1zEUR5jpdbL83Fbq
-----END RSA PRIVATE KEY-----

View file

@ -1,9 +0,0 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4f5wg5l2hKsTeNem/V41
fGnJm6gOdrj8ym3rFkEU/wT8RDtnSgFEZOQpHEgQ7JL38xUfU0Y3g6aYw9QT0hJ7
mCpz9Er5qLaMXJwZxzHzAahlfA0icqabvJOMvQtzD6uQv6wPEyZtDTWiQi9AXwBp
HssPnpYGIn20ZZuNlX2BrClciHhCPUIIZOQn/MmqTD31jSyjoQoV7MhhMTATKJx2
XrHhR+1DcKJzQBSTAGnpYVaqpsARap+nwRipr3nUTuxyGohBTSmjJ2usSeQXHI3b
ODIRe1AuTyHceAbewn8b462yEWKARdpd9AjQW5SIVPfdsz5B6GlYQ5LdYKtznTuy
7wIDAQAB
-----END PUBLIC KEY-----

View file

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Matthew Dillon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,145 @@
# jwt
[![GoDoc](https://godoc.org/github.com/thermokarst/jwt?status.svg)](https://godoc.org/github.com/thermokarst/jwt)
A simple, opinionated Go net/http middleware for integrating JSON Web Tokens into
your application:
```go
package main
import (
"errors"
"fmt"
"net/http"
"time"
"github.com/thermokarst/jwt"
)
func protectMe(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "secured")
}
func main() {
var authFunc = func(email string, password string) error {
// Hard-code a user --- this could easily be a database call, etc.
if email != "test" || password != "test" {
return errors.New("invalid credentials")
}
return nil
}
var claimsFunc = func(userId string) (map[string]interface{}, error) {
currentTime := time.Now()
return map[string]interface{}{
"iat": currentTime.Unix(),
"exp": currentTime.Add(time.Minute * 60 * 24).Unix(),
"sub": userId,
}, nil
}
var verifyClaimsFunc = func(claims []byte) error {
currentTime := time.Now()
var c struct {
Exp int64
Iat int64
Sub string
}
err := json.Unmarshal(claims, &c)
if err != nil {
return err
}
if currentTime.After(time.Unix(c.Exp, 0)) {
return errors.New("this token has expired!")
}
if c.Sub != "test" {
return errors.New("who are you??!")
}
return nil
}
config := &jwt.Config{
Secret: "password",
Auth: authFunc,
Claims: claimsFunc,
}
j, err := jwt.New(config)
if err != nil {
panic(err)
}
protect := http.HandlerFunc(protectMe)
http.Handle("/authenticate", j.GenerateToken())
http.Handle("/secure", j.Secure(protect, verifyClaimsFunc))
http.ListenAndServe(":8080", nil)
}
```
# Installation
$ go get github.com/thermokarst/jwt
# Usage
**This is a work in progress**
Create a new instance of the middleware by passing in a configuration for your
app. The config includes a shared secret (this middleware only builds HS256
tokens), a function for authenticating user, and a function for generating a
user's claims. The idea here is to be dead-simple for someone to drop this into
a project and hit the ground running.
```go
config := &jwt.Config{
Secret: "password",
Auth: authFunc, // func(string, string) error
Claims: claimsFunc, // func(string) (map[string]interface{})
}
j, err := jwt.New(config)
```
Once the middleware is instantiated, create a route for users to generate a JWT
at.
```go
http.Handle("/authenticate", j.GenerateToken())
```
The auth function takes two arguments (the identity, and the authorization
key), POSTed as a JSON-encoded body:
{"email":"user@example.com","password":"mypassword"}
These fields are static for now, but will be customizable in a later release.
The claims are generated using the claims function provided in the
configuration. This function is only run if the auth function verifies the
user's identity, then the user's unique identifier (primary key id, UUID,
email, whatever you want) is passed as a string to the claims function. Your
function should return a `map[string]interface{}` with the desired claimset.
Routes are "secured" by calling the `Secure(http.Handler, jwt.VerifyClaimsFunc)`
handler:
```go
http.Handle("/secureendpoint", j.Secure(someHandler, verifyClaimsFunc))
```
The claims verification function is called after the token has been parsed and
validated: this is where you control how your application handles the claims
contained within the JWT.
# Motivation
This work was prepared for a crypto/security class at the University of Alaska
Fairbanks. I hope to use this in some of my projects, but please proceed with
caution if you adopt this for your own work. As well, the API is still quite
unstable, so be prepared for handling any changes.
# Tests
$ go test
# Contributors
Matthew Ryan Dillon (matthewrdillon@gmail.com)

View file

@ -0,0 +1,57 @@
package main
import (
"errors"
"fmt"
"net/http"
"time"
"github.com/thermokarst/jwt"
)
func protectMe(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "secured")
}
func dontProtectMe(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "not secured")
}
func main() {
var authFunc = func(email string, password string) error {
// Hard-code a user
if email != "test" || password != "test" {
return errors.New("invalid credentials")
}
return nil
}
var claimsFunc = func(string) (map[string]interface{}, error) {
currentTime := time.Now()
return map[string]interface{}{
"iat": currentTime.Unix(),
"exp": currentTime.Add(time.Minute * 60 * 24).Unix(),
}, nil
}
var verifyClaimsFunc = func([]byte) error {
// We don't really care about the claims, just approve as-is
return nil
}
config := &jwt.Config{
Secret: "password",
Auth: authFunc,
Claims: claimsFunc,
}
j, err := jwt.New(config)
if err != nil {
panic(err)
}
protect := http.HandlerFunc(protectMe)
dontProtect := http.HandlerFunc(dontProtectMe)
http.Handle("/authenticate", j.GenerateToken())
http.Handle("/secure", j.Secure(protect, verifyClaimsFunc))
http.Handle("/insecure", dontProtect)
http.ListenAndServe(":8080", nil)
}

275
Godeps/_workspace/src/github.com/thermokarst/jwt/jwt.go generated vendored Normal file
View file

@ -0,0 +1,275 @@
// Package jwt implements a simple, opinionated net/http-compatible middleware for
// integrating JSON Web Tokens (JWT).
package jwt
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"strings"
)
const (
typ = "JWT"
alg = "HS256"
)
// Errors introduced by this package.
var (
ErrMissingConfig = errors.New("missing configuration")
ErrMissingSecret = errors.New("please provide a shared secret")
ErrMissingAuthFunc = errors.New("please provide an auth function")
ErrMissingClaimsFunc = errors.New("please provide a claims function")
ErrEncoding = errors.New("error encoding value")
ErrDecoding = errors.New("error decoding value")
ErrMissingToken = errors.New("please provide a token")
ErrMalformedToken = errors.New("please provide a valid token")
ErrInvalidSignature = errors.New("signature could not be verified")
ErrParsingCredentials = errors.New("error parsing credentials")
)
// Config is a container for setting up the JWT middleware.
type Config struct {
Secret string
Auth AuthFunc
Claims ClaimsFunc
}
// AuthFunc is a type for delegating user authentication to the client-code.
type AuthFunc func(string, string) error
// ClaimsFunc is a type for delegating claims generation to the client-code.
type ClaimsFunc func(string) (map[string]interface{}, error)
// VerifyClaimsFunc is a type for processing and validating JWT claims on one
// or more route's in the client-code.
type VerifyClaimsFunc func([]byte) error
// Middleware is where we store all the specifics related to the client's
// JWT needs.
type Middleware struct {
secret string
auth AuthFunc
claims ClaimsFunc
}
// New creates a new Middleware from a user-specified configuration.
func New(c *Config) (*Middleware, error) {
if c == nil {
return nil, ErrMissingConfig
}
if c.Secret == "" {
return nil, ErrMissingSecret
}
if c.Auth == nil {
return nil, ErrMissingAuthFunc
}
if c.Claims == nil {
return nil, ErrMissingClaimsFunc
}
m := &Middleware{
secret: c.Secret,
auth: c.Auth,
claims: c.Claims,
}
return m, nil
}
// Secure wraps a client-specified http.Handler with a verification function,
// as well as-built in parsing of the request's JWT. This allows each handler
// to have it's own verification/validation protocol.
func (m *Middleware) Secure(h http.Handler, v VerifyClaimsFunc) http.Handler {
secureHandler := func(w http.ResponseWriter, r *http.Request) *jwtError {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
return &jwtError{status: http.StatusUnauthorized, err: ErrMissingToken}
}
token := strings.Split(authHeader, " ")[1]
tokenParts := strings.Split(token, ".")
if len(tokenParts) != 3 {
return &jwtError{status: http.StatusUnauthorized, err: ErrMalformedToken}
}
// First, verify JOSE header
header, err := decode(tokenParts[0])
if err != nil {
return &jwtError{
status: http.StatusInternalServerError,
err: err,
message: fmt.Sprintf("decoding header (%v)", tokenParts[0]),
}
}
var t struct {
Typ string
Alg string
}
err = json.Unmarshal(header, &t)
if err != nil {
return &jwtError{
status: http.StatusInternalServerError,
err: ErrMalformedToken,
message: fmt.Sprintf("unmarshalling header (%s)", header),
}
}
// Then, verify signature
mac := hmac.New(sha256.New, []byte(m.secret))
message := []byte(strings.Join([]string{tokenParts[0], tokenParts[1]}, "."))
mac.Write(message)
expectedMac, err := encode(mac.Sum(nil))
if err != nil {
return &jwtError{status: http.StatusInternalServerError, err: err}
}
if !hmac.Equal([]byte(tokenParts[2]), []byte(expectedMac)) {
return &jwtError{
status: http.StatusUnauthorized,
err: ErrInvalidSignature,
message: fmt.Sprintf("checking signature (%v)", tokenParts[2]),
}
}
// Finally, check claims
claimSet, err := decode(tokenParts[1])
if err != nil {
return &jwtError{
status: http.StatusInternalServerError,
err: ErrDecoding,
message: "decoding claims",
}
}
err = v(claimSet)
if err != nil {
return &jwtError{
status: http.StatusUnauthorized,
err: err,
message: "handling claims callback",
}
}
// If we make it this far, process the downstream handler
h.ServeHTTP(w, r)
return nil
}
return errorHandler(secureHandler)
}
// GenerateToken returns a middleware that parsing an incoming request for a JWT,
// calls the client-supplied auth function, and if successful, returns a JWT to
// the requester.
func (m *Middleware) GenerateToken() http.Handler {
generateHandler := func(w http.ResponseWriter, r *http.Request) *jwtError {
var b map[string]string
err := json.NewDecoder(r.Body).Decode(&b)
if err != nil {
return &jwtError{
status: http.StatusInternalServerError,
err: ErrParsingCredentials,
message: "parsing authorization",
}
}
err = m.auth(b["email"], b["password"])
if err != nil {
return &jwtError{
status: http.StatusInternalServerError,
err: err,
message: "performing authorization",
}
}
// For now, the header will be static
header, err := encode(fmt.Sprintf(`{"typ":%q,"alg":%q}`, typ, alg))
if err != nil {
return &jwtError{
status: http.StatusInternalServerError,
err: ErrEncoding,
message: "encoding header",
}
}
// Generate claims for user
claims, err := m.claims(b["email"])
if err != nil {
return &jwtError{
status: http.StatusInternalServerError,
err: err,
message: "generating claims",
}
}
claimsJSON, err := json.Marshal(claims)
if err != nil {
return &jwtError{
status: http.StatusInternalServerError,
err: ErrEncoding,
message: "marshalling claims",
}
}
claimsSet, err := encode(claimsJSON)
if err != nil {
return &jwtError{
status: http.StatusInternalServerError,
err: ErrEncoding,
message: "encoding claims",
}
}
toSig := strings.Join([]string{header, claimsSet}, ".")
h := hmac.New(sha256.New, []byte(m.secret))
h.Write([]byte(toSig))
sig, err := encode(h.Sum(nil))
if err != nil {
return &jwtError{
status: http.StatusInternalServerError,
err: ErrEncoding,
message: "encoding signature",
}
}
response := strings.Join([]string{toSig, sig}, ".")
w.Write([]byte(response))
return nil
}
return errorHandler(generateHandler)
}
type jwtError struct {
status int
err error
message string
}
type errorHandler func(http.ResponseWriter, *http.Request) *jwtError
func (e errorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := e(w, r); err != nil {
if err.message != "" {
log.Printf("error (%v) while %s", err.err, err.message)
}
http.Error(w, err.err.Error(), err.status)
}
}
func encode(s interface{}) (string, error) {
var r []byte
switch v := s.(type) {
case string:
r = []byte(v)
case []byte:
r = v
default:
return "", ErrEncoding
}
return base64.StdEncoding.EncodeToString(r), nil
}
func decode(s string) ([]byte, error) {
return base64.StdEncoding.DecodeString(s)
}

View file

@ -0,0 +1,217 @@
package jwt
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
var testHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("test"))
})
var authFunc = func(email, password string) error {
return nil
}
var claimsFunc = func(id string) (map[string]interface{}, error) {
currentTime := time.Now()
return map[string]interface{}{
"iat": currentTime.Unix(),
"exp": currentTime.Add(time.Minute * 60 * 24).Unix(),
}, nil
}
var verifyClaimsFunc = func(claims []byte) error {
currentTime := time.Now()
var c struct {
Exp int64
Iat int64
}
err := json.Unmarshal(claims, &c)
if err != nil {
return err
}
if currentTime.After(time.Unix(c.Exp, 0)) {
return errors.New("expired")
}
return nil
}
func newMiddlewareOrFatal(t *testing.T) *Middleware {
config := &Config{
Secret: "password",
Auth: authFunc,
Claims: claimsFunc,
}
middleware, err := New(config)
if err != nil {
t.Fatalf("new middleware: %v", err)
}
return middleware
}
func newToken(t *testing.T) (string, *Middleware) {
middleware := newMiddlewareOrFatal(t)
authBody := map[string]interface{}{
"email": "user@example.com",
"password": "password",
}
body, err := json.Marshal(authBody)
if err != nil {
t.Error(err)
}
ts := httptest.NewServer(middleware.GenerateToken())
defer ts.Close()
resp, err := http.Post(ts.URL, "application/json", bytes.NewReader(body))
respBody, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
t.Error(err)
}
return string(respBody), middleware
}
func TestNewJWTMiddleware(t *testing.T) {
middleware := newMiddlewareOrFatal(t)
if middleware.secret != "password" {
t.Errorf("wanted password, got %v", middleware.secret)
}
err := middleware.auth("", "")
if err != nil {
t.Fatal(err)
}
claimsVal, err := middleware.claims("1")
if err != nil {
t.Fatal(err)
}
if _, ok := claimsVal["iat"]; !ok {
t.Errorf("wanted a claims set, got %v", claimsVal)
}
}
func TestNewJWTMiddlewareNoConfig(t *testing.T) {
cases := map[*Config]error{
nil: ErrMissingConfig,
&Config{}: ErrMissingSecret,
&Config{
Auth: authFunc,
Claims: claimsFunc,
}: ErrMissingSecret,
&Config{
Secret: "secret",
Claims: claimsFunc,
}: ErrMissingAuthFunc,
&Config{
Auth: authFunc,
Secret: "secret",
}: ErrMissingClaimsFunc,
}
for config, jwtErr := range cases {
_, err := New(config)
if err != jwtErr {
t.Errorf("wanted error: %v, got error: %v using config: %v", jwtErr, err, config)
}
}
}
func TestGenerateTokenHandler(t *testing.T) {
token, m := newToken(t)
j := strings.Split(token, ".")
header := base64.StdEncoding.EncodeToString([]byte(`{"typ":"JWT","alg":"HS256"}`))
if j[0] != header {
t.Errorf("wanted %v, got %v", header, j[0])
}
claims, err := base64.StdEncoding.DecodeString(j[1])
var c struct {
Exp int
Iat int
}
err = json.Unmarshal(claims, &c)
if err != nil {
t.Error(err)
}
duration := time.Duration(c.Exp-c.Iat) * time.Second
d := time.Minute * 60 * 24
if duration != d {
t.Errorf("wanted %v, got %v", d, duration)
}
mac := hmac.New(sha256.New, []byte(m.secret))
message := []byte(strings.Join([]string{j[0], j[1]}, "."))
mac.Write(message)
expectedMac := base64.StdEncoding.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(j[2]), []byte(expectedMac)) {
t.Errorf("wanted %v, got %v", expectedMac, j[2])
}
}
func TestSecureHandlerNoToken(t *testing.T) {
middleware := newMiddlewareOrFatal(t)
resp := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "http://example.com", nil)
middleware.Secure(testHandler, verifyClaimsFunc).ServeHTTP(resp, req)
body := strings.TrimSpace(resp.Body.String())
if body != ErrMissingToken.Error() {
t.Errorf("wanted %q, got %q", ErrMissingToken.Error(), body)
}
}
func TestSecureHandlerBadToken(t *testing.T) {
middleware := newMiddlewareOrFatal(t)
resp := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "http://example.com", nil)
req.Header.Set("Authorization", "Bearer abcdefg")
middleware.Secure(testHandler, verifyClaimsFunc).ServeHTTP(resp, req)
body := strings.TrimSpace(resp.Body.String())
if body != ErrMalformedToken.Error() {
t.Errorf("wanted %q, got %q", ErrMalformedToken.Error(), body)
}
resp = httptest.NewRecorder()
req, _ = http.NewRequest("GET", "http://example.com", nil)
req.Header.Set("Authorization", "Bearer abcd.abcd.abcd")
middleware.Secure(testHandler, verifyClaimsFunc).ServeHTTP(resp, req)
body = strings.TrimSpace(resp.Body.String())
if body != ErrMalformedToken.Error() {
t.Errorf("wanted %q, got %q", ErrMalformedToken.Error(), body)
}
}
func TestSecureHandlerBadSignature(t *testing.T) {
token, middleware := newToken(t)
parts := strings.Split(token, ".")
token = strings.Join([]string{parts[0], parts[1], "abcd"}, ".")
resp := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "http://example.com", nil)
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
middleware.Secure(testHandler, verifyClaimsFunc).ServeHTTP(resp, req)
body := strings.TrimSpace(resp.Body.String())
if body != ErrInvalidSignature.Error() {
t.Errorf("wanted %s, got %s", ErrInvalidSignature.Error(), body)
}
}
func TestSecureHandlerGoodToken(t *testing.T) {
token, middleware := newToken(t)
resp := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "http://example.com", nil)
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
middleware.Secure(testHandler, verifyClaimsFunc).ServeHTTP(resp, req)
body := strings.TrimSpace(resp.Body.String())
if body != "test" {
t.Errorf("wanted %s, got %s", "test", body)
}
}

38
auth.go
View file

@ -1,38 +0,0 @@
package main
import (
"errors"
"os"
)
const (
tokenName = "AccessToken"
)
var (
verifyKey, signKey []byte
errWhileSigningToken = errors.New("error while signing token")
errPleaseLogIn = errors.New("please log in")
errWhileParsingCookie = errors.New("error while parsing cookie")
errTokenExpired = errors.New("token expired")
errGenericError = errors.New("generic error")
errAccessDenied = errors.New("insufficient privileges")
)
func setupCerts() error {
// openssl genrsa -out app.rsa 1024
signkey := os.Getenv("PRIVATE_KEY")
if signkey == "" {
return errors.New("please set PRIVATE_KEY")
}
signKey = []byte(signkey)
// openssl rsa -in app.rsa -pubout > app.rsa.pub
verifykey := os.Getenv("PUBLIC_KEY")
if verifykey == "" {
return errors.New("please set PUBLIC_KEY")
}
verifyKey = []byte(verifykey)
return nil
}

View file

@ -1,42 +1,109 @@
package main
import (
"encoding/json"
"errors"
"net/http"
"os"
"strings"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/gorilla/mux"
"github.com/thermokarst/jwt"
)
func Handler() http.Handler {
claimsFunc := func(email string) (map[string]interface{}, error) {
currentTime := time.Now()
user, err := dbGetUserByEmail(email)
if err != nil {
return nil, err
}
return map[string]interface{}{
"name": user.Name,
"iss": "bactdb",
"sub": user.Id,
"role": user.Role,
"iat": currentTime.Unix(),
"exp": currentTime.Add(time.Minute * 60 * 24).Unix(),
}, nil
}
verifyClaims := func(claims []byte) error {
currentTime := time.Now()
var c struct {
Name string
Iss string
Sub int64
Role string
Iat int64
Exp int64
}
err := json.Unmarshal(claims, &c)
if err != nil {
return err
}
if currentTime.After(time.Unix(c.Exp, 0)) {
return errors.New("this token has expired")
}
return nil
}
config := &jwt.Config{
Secret: os.Getenv("SECRET"),
Auth: dbAuthenticate,
Claims: claimsFunc,
}
j, err := jwt.New(config)
if err != nil {
panic(err)
}
m := mux.NewRouter()
// Non-auth routes
m.HandleFunc("/authenticate", serveAuthenticateUser).Methods("POST")
m.Handle("/authenticate", tokenHandler(j.GenerateToken())).Methods("POST")
// Auth routes
m.Handle("/users", authHandler(serveUsersList)).Methods("GET")
m.Handle("/users/{Id:.+}", authHandler(serveUser)).Methods("GET")
m.Handle("/users", j.Secure(http.HandlerFunc(serveUsersList), verifyClaims)).Methods("GET")
m.Handle("/users/{Id:.+}", j.Secure(http.HandlerFunc(serveUser), verifyClaims)).Methods("GET")
// Path-based pattern matching subrouter
s := m.PathPrefix("/{genus}").Subrouter()
// Strains
s.Handle("/strains", authHandler(serveStrainsList)).Methods("GET")
s.Handle("/strains/{Id:.+}", authHandler(serveStrain)).Methods("GET")
type r struct {
f http.HandlerFunc
m string
}
// Measurements
s.Handle("/measurements", authHandler(serveMeasurementsList)).Methods("GET")
s.Handle("/measurements/{Id:.+}", authHandler(serveMeasurement)).Methods("GET")
routes := map[string]r{
"/strains": r{serveStrainsList, "GET"},
"/strains/{Id:.+}": r{serveStrain, "GET"},
"/measurements": r{serveMeasurementsList, "GET"},
"/measurements/{Id:.+}": r{serveMeasurement, "GET"},
"/characteristics": r{serveCharacteristicsList, "GET"},
"/characteristics/{Id:.+}": r{serveCharacteristic, "GET"},
}
// Characteristics
s.Handle("/characteristics", authHandler(serveCharacteristicsList)).Methods("GET")
s.Handle("/characteristics/{Id:.+}", authHandler(serveCharacteristic)).Methods("GET")
for path, route := range routes {
s.Handle(path, j.Secure(http.HandlerFunc(route.f), verifyClaims)).Methods(route.m)
}
return corsHandler(m)
}
func tokenHandler(h http.Handler) http.Handler {
token := func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
// Hackish, but we want the token in a JSON object
w.Write([]byte(`{"token":"`))
h.ServeHTTP(w, r)
w.Write([]byte(`"}`))
}
return http.HandlerFunc(token)
}
func corsHandler(h http.Handler) http.Handler {
cors := func(w http.ResponseWriter, r *http.Request) {
domains := os.Getenv("DOMAINS")
@ -56,45 +123,3 @@ func corsHandler(h http.Handler) http.Handler {
}
return http.HandlerFunc(cors)
}
// Only accessible with a valid token
func authHandler(f func(http.ResponseWriter, *http.Request)) http.Handler {
h := http.HandlerFunc(f)
auth := func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
http.Error(w, errPleaseLogIn.Error(), http.StatusUnauthorized)
return
}
s := strings.Split(authHeader, " ")
// Validate the token
token, err := jwt.Parse(s[1], func(token *jwt.Token) (interface{}, error) {
return []byte(verifyKey), nil
})
// Branch out into the possible error from signing
switch err.(type) {
case nil: // No error
if !token.Valid { // But may still be invalid
http.Error(w, errPleaseLogIn.Error(), http.StatusUnauthorized)
return
}
case *jwt.ValidationError: // Something was wrong during the validation
vErr := err.(*jwt.ValidationError)
switch vErr.Errors {
case jwt.ValidationErrorExpired:
http.Error(w, errTokenExpired.Error(), http.StatusUnauthorized)
return
default:
http.Error(w, errGenericError.Error(), http.StatusInternalServerError)
return
}
default: // Something else went wrong
http.Error(w, errGenericError.Error(), http.StatusInternalServerError)
return
}
h.ServeHTTP(w, r)
}
return http.HandlerFunc(auth)
}

View file

@ -90,11 +90,6 @@ func cmdServe(c *cli.Context) {
}
httpAddr := fmt.Sprintf(":%v", addr)
err = setupCerts()
if err != nil {
log.Fatal("SetupCerts: ", err)
}
m := http.NewServeMux()
m.Handle("/api/", http.StripPrefix("/api", Handler()))

View file

@ -1,26 +1,6 @@
#!/bin/sh
PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCcjobUzMiX4DUmesqDtIWSZoYc2O2OYN3PwAbSxm+isuCMcXtW
YfjTtGmYlcVIJUVEv52z21bIQfKjIV/pKjq0Rd6xoVtsuupcQHilgOVX9t01Y55G
VyyUXDz3JgSPrBRM/z6UaU+hwq+xaPAiDn5P3/s419O1WedEyach6Ep4mQIDAQAB
AoGAQdiARQhMZfRa5nBGtNY8R7LvPTrPz05WfIZbWFM1qMxrPSaNpWtXaFM9BnwX
mZxzYdLl1TuvaFK0ZoAnAr5MKdW/CETjhTt8qfIkaJIEZBWTDv2luJKXNFFpGSmq
jvWken3wYM7txPh7p/ApHRVMkiOsaKjZRCy1N5l3hEE9ivECQQDKoZ0mBCreBWB9
fEK6L+BxTDDkwo0G0qgnGZZjlbOBLKsZL2vVoHXc4g0+tQUsOdjjFwPi6kUGXYTM
I8qTJb3/AkEAxcpXpknAjaLX4s0uJj/eL5KJE7YbiQtJVjA+vd2/JtLTxL8JrLMJ
N0Rn1dEJzbVLVZ5vrdOCxYBGHRlDHbL5ZwJBAJRAbCadU/O+wVruKC/qyW57TSaB
xQah54875FEV/RBcaw5xKJdS4Ajshr5DWPaDmFCFzT0fI8NFdtyYryS7r2cCQBnT
MqCOrqqPoZqGackqu6sAeg9tzqiVJa0wPXDy/Btomaftvaij88cYkmozkhEe48g5
GKHcpQ1+kykHfGDrVm8CQD1vzUvgedfKFJHPC+I5GSVNxmgY9zqjVJ15Flmdjx8n
pUL6zWr75vw+ZZDQWNOa8gdiE/l9fomcVTUA49fui94=
-----END RSA PRIVATE KEY-----" \
PUBLIC_KEY="-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCcjobUzMiX4DUmesqDtIWSZoYc
2O2OYN3PwAbSxm+isuCMcXtWYfjTtGmYlcVIJUVEv52z21bIQfKjIV/pKjq0Rd6x
oVtsuupcQHilgOVX9t01Y55GVyyUXDz3JgSPrBRM/z6UaU+hwq+xaPAiDn5P3/s4
19O1WedEyach6Ep4mQIDAQAB
-----END PUBLIC KEY-----" \
SECRET=secret \
PGDATABASE=bactdbtest \
DOMAINS="http://localhost:4200" \
bash -c 'go run *.go migrate --drop'

View file

@ -1,26 +1,6 @@
#!/bin/sh
PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCcjobUzMiX4DUmesqDtIWSZoYc2O2OYN3PwAbSxm+isuCMcXtW
YfjTtGmYlcVIJUVEv52z21bIQfKjIV/pKjq0Rd6xoVtsuupcQHilgOVX9t01Y55G
VyyUXDz3JgSPrBRM/z6UaU+hwq+xaPAiDn5P3/s419O1WedEyach6Ep4mQIDAQAB
AoGAQdiARQhMZfRa5nBGtNY8R7LvPTrPz05WfIZbWFM1qMxrPSaNpWtXaFM9BnwX
mZxzYdLl1TuvaFK0ZoAnAr5MKdW/CETjhTt8qfIkaJIEZBWTDv2luJKXNFFpGSmq
jvWken3wYM7txPh7p/ApHRVMkiOsaKjZRCy1N5l3hEE9ivECQQDKoZ0mBCreBWB9
fEK6L+BxTDDkwo0G0qgnGZZjlbOBLKsZL2vVoHXc4g0+tQUsOdjjFwPi6kUGXYTM
I8qTJb3/AkEAxcpXpknAjaLX4s0uJj/eL5KJE7YbiQtJVjA+vd2/JtLTxL8JrLMJ
N0Rn1dEJzbVLVZ5vrdOCxYBGHRlDHbL5ZwJBAJRAbCadU/O+wVruKC/qyW57TSaB
xQah54875FEV/RBcaw5xKJdS4Ajshr5DWPaDmFCFzT0fI8NFdtyYryS7r2cCQBnT
MqCOrqqPoZqGackqu6sAeg9tzqiVJa0wPXDy/Btomaftvaij88cYkmozkhEe48g5
GKHcpQ1+kykHfGDrVm8CQD1vzUvgedfKFJHPC+I5GSVNxmgY9zqjVJ15Flmdjx8n
pUL6zWr75vw+ZZDQWNOa8gdiE/l9fomcVTUA49fui94=
-----END RSA PRIVATE KEY-----" \
PUBLIC_KEY="-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCcjobUzMiX4DUmesqDtIWSZoYc
2O2OYN3PwAbSxm+isuCMcXtWYfjTtGmYlcVIJUVEv52z21bIQfKjIV/pKjq0Rd6x
oVtsuupcQHilgOVX9t01Y55GVyyUXDz3JgSPrBRM/z6UaU+hwq+xaPAiDn5P3/s4
19O1WedEyach6Ep4mQIDAQAB
-----END PUBLIC KEY-----" \
SECRET=secret \
PGDATABASE=bactdbtest \
DOMAINS="http://localhost:4200" \
bash -c 'go run *.go serve'

View file

@ -8,13 +8,13 @@ import (
"strconv"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/gorilla/mux"
"golang.org/x/crypto/bcrypt"
)
var (
ErrUserNotFound = errors.New("user not found")
ErrUserNotFound = errors.New("user not found")
ErrInvalidEmailOrPassword = errors.New("Invalid email or password")
)
func init() {
@ -87,65 +87,16 @@ func serveUser(w http.ResponseWriter, r *http.Request) {
w.Write(data)
}
func serveAuthenticateUser(w http.ResponseWriter, r *http.Request) {
var a struct {
Email string
Password string
func dbAuthenticate(email string, password string) error {
var user User
q := `SELECT * FROM users WHERE lower(email)=lower($1);`
if err := DBH.SelectOne(&user, q, email); err != nil {
return ErrInvalidEmailOrPassword
}
if err := json.NewDecoder(r.Body).Decode(&a); err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
return ErrInvalidEmailOrPassword
}
user_session, err := dbAuthenticate(a.Email, a.Password)
if err != nil {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"error":"Invalid email or password"}`))
return
}
currentTime := time.Now()
t := jwt.New(jwt.SigningMethodRS256)
t.Claims["name"] = user_session.Name
t.Claims["iss"] = "bactdb"
t.Claims["sub"] = user_session.Email
t.Claims["role"] = user_session.Role
t.Claims["iat"] = currentTime.Unix()
t.Claims["exp"] = currentTime.Add(time.Minute * 60 * 24).Unix()
tokenString, err := t.SignedString(signKey)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
token := struct {
Token string `json:"token"`
UserID int64 `json:"user_id"`
}{
Token: tokenString,
UserID: user_session.Id,
}
data, err := json.Marshal(token)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.Write(data)
}
func dbAuthenticate(email string, password string) (*User, error) {
var users []User
if err := DBH.Select(&users, `SELECT * FROM users WHERE lower(email)=lower($1);`, email); err != nil {
return nil, err
}
if len(users) == 0 {
return nil, ErrUserNotFound
}
if err := bcrypt.CompareHashAndPassword([]byte(users[0].Password), []byte(password)); err != nil {
return nil, err
}
return &users[0], nil
return nil
}
func dbGetUsers(opt *ListOptions) ([]*User, error) {
@ -168,3 +119,15 @@ func dbGetUser(id int64) (*User, error) {
}
return &user, nil
}
func dbGetUserByEmail(email string) (*User, error) {
var user User
q := `SELECT * FROM users WHERE lower(email)=lower($1);`
if err := DBH.SelectOne(&user, q, email); err != nil {
if err == sql.ErrNoRows {
return nil, ErrUserNotFound
}
return nil, err
}
return &user, nil
}