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