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