1package jwt_test 2 3// Example HTTP auth using asymmetric crypto/RSA keys 4// This is based on a (now outdated) example at https://gist.github.com/cryptix/45c33ecf0ae54828e63b 5 6import ( 7 "bytes" 8 "crypto/rsa" 9 "fmt" 10 "github.com/dgrijalva/jwt-go" 11 "github.com/dgrijalva/jwt-go/request" 12 "io" 13 "io/ioutil" 14 "log" 15 "net" 16 "net/http" 17 "net/url" 18 "strings" 19 "time" 20) 21 22// location of the files used for signing and verification 23const ( 24 privKeyPath = "test/sample_key" // openssl genrsa -out app.rsa keysize 25 pubKeyPath = "test/sample_key.pub" // openssl rsa -in app.rsa -pubout > app.rsa.pub 26) 27 28var ( 29 verifyKey *rsa.PublicKey 30 signKey *rsa.PrivateKey 31 serverPort int 32 // storing sample username/password pairs 33 // don't do this on a real server 34 users = map[string]string{ 35 "test": "known", 36 } 37) 38 39// read the key files before starting http handlers 40func init() { 41 signBytes, err := ioutil.ReadFile(privKeyPath) 42 fatal(err) 43 44 signKey, err = jwt.ParseRSAPrivateKeyFromPEM(signBytes) 45 fatal(err) 46 47 verifyBytes, err := ioutil.ReadFile(pubKeyPath) 48 fatal(err) 49 50 verifyKey, err = jwt.ParseRSAPublicKeyFromPEM(verifyBytes) 51 fatal(err) 52 53 http.HandleFunc("/authenticate", authHandler) 54 http.HandleFunc("/restricted", restrictedHandler) 55 56 // Setup listener 57 listener, err := net.ListenTCP("tcp", &net.TCPAddr{}) 58 serverPort = listener.Addr().(*net.TCPAddr).Port 59 60 log.Println("Listening...") 61 go func() { 62 fatal(http.Serve(listener, nil)) 63 }() 64} 65 66var start func() 67 68func fatal(err error) { 69 if err != nil { 70 log.Fatal(err) 71 } 72} 73 74// Define some custom types were going to use within our tokens 75type CustomerInfo struct { 76 Name string 77 Kind string 78} 79 80type CustomClaimsExample struct { 81 *jwt.StandardClaims 82 TokenType string 83 CustomerInfo 84} 85 86func Example_getTokenViaHTTP() { 87 // See func authHandler for an example auth handler that produces a token 88 res, err := http.PostForm(fmt.Sprintf("http://localhost:%v/authenticate", serverPort), url.Values{ 89 "user": {"test"}, 90 "pass": {"known"}, 91 }) 92 if err != nil { 93 fatal(err) 94 } 95 96 if res.StatusCode != 200 { 97 fmt.Println("Unexpected status code", res.StatusCode) 98 } 99 100 // Read the token out of the response body 101 buf := new(bytes.Buffer) 102 io.Copy(buf, res.Body) 103 res.Body.Close() 104 tokenString := strings.TrimSpace(buf.String()) 105 106 // Parse the token 107 token, err := jwt.ParseWithClaims(tokenString, &CustomClaimsExample{}, func(token *jwt.Token) (interface{}, error) { 108 // since we only use the one private key to sign the tokens, 109 // we also only use its public counter part to verify 110 return verifyKey, nil 111 }) 112 fatal(err) 113 114 claims := token.Claims.(*CustomClaimsExample) 115 fmt.Println(claims.CustomerInfo.Name) 116 117 //Output: test 118} 119 120func Example_useTokenViaHTTP() { 121 122 // Make a sample token 123 // In a real world situation, this token will have been acquired from 124 // some other API call (see Example_getTokenViaHTTP) 125 token, err := createToken("foo") 126 fatal(err) 127 128 // Make request. See func restrictedHandler for example request processor 129 req, err := http.NewRequest("GET", fmt.Sprintf("http://localhost:%v/restricted", serverPort), nil) 130 fatal(err) 131 req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", token)) 132 res, err := http.DefaultClient.Do(req) 133 fatal(err) 134 135 // Read the response body 136 buf := new(bytes.Buffer) 137 io.Copy(buf, res.Body) 138 res.Body.Close() 139 fmt.Println(buf.String()) 140 141 // Output: Welcome, foo 142} 143 144func createToken(user string) (string, error) { 145 // create a signer for rsa 256 146 t := jwt.New(jwt.GetSigningMethod("RS256")) 147 148 // set our claims 149 t.Claims = &CustomClaimsExample{ 150 &jwt.StandardClaims{ 151 // set the expire time 152 // see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-20#section-4.1.4 153 ExpiresAt: time.Now().Add(time.Minute * 1).Unix(), 154 }, 155 "level1", 156 CustomerInfo{user, "human"}, 157 } 158 159 // Creat token string 160 return t.SignedString(signKey) 161} 162 163// reads the form values, checks them and creates the token 164func authHandler(w http.ResponseWriter, r *http.Request) { 165 // make sure its post 166 if r.Method != "POST" { 167 w.WriteHeader(http.StatusBadRequest) 168 fmt.Fprintln(w, "No POST", r.Method) 169 return 170 } 171 172 user := r.FormValue("user") 173 pass := r.FormValue("pass") 174 175 log.Printf("Authenticate: user[%s] pass[%s]\n", user, pass) 176 177 // check values 178 if user != "test" || pass != "known" { 179 w.WriteHeader(http.StatusForbidden) 180 fmt.Fprintln(w, "Wrong info") 181 return 182 } 183 184 tokenString, err := createToken(user) 185 if err != nil { 186 w.WriteHeader(http.StatusInternalServerError) 187 fmt.Fprintln(w, "Sorry, error while Signing Token!") 188 log.Printf("Token Signing error: %v\n", err) 189 return 190 } 191 192 w.Header().Set("Content-Type", "application/jwt") 193 w.WriteHeader(http.StatusOK) 194 fmt.Fprintln(w, tokenString) 195} 196 197// only accessible with a valid token 198func restrictedHandler(w http.ResponseWriter, r *http.Request) { 199 // Get token from request 200 token, err := request.ParseFromRequestWithClaims(r, request.OAuth2Extractor, &CustomClaimsExample{}, func(token *jwt.Token) (interface{}, error) { 201 // since we only use the one private key to sign the tokens, 202 // we also only use its public counter part to verify 203 return verifyKey, nil 204 }) 205 206 // If the token is missing or invalid, return error 207 if err != nil { 208 w.WriteHeader(http.StatusUnauthorized) 209 fmt.Fprintln(w, "Invalid token:", err) 210 return 211 } 212 213 // Token is valid 214 fmt.Fprintln(w, "Welcome,", token.Claims.(*CustomClaimsExample).Name) 215 return 216} 217