2016-06-08 21:36:38 +03:00
|
|
|
package jwt_test
|
|
|
|
|
2016-06-08 21:38:48 +03:00
|
|
|
// Example HTTP auth using asymmetric crypto/RSA keys
|
|
|
|
// This is based on a (now outdated) example at https://gist.github.com/cryptix/45c33ecf0ae54828e63b
|
2016-06-08 21:36:38 +03:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"crypto/rsa"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"log"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
2022-05-28 02:11:16 +03:00
|
|
|
"os"
|
2016-06-08 21:36:38 +03:00
|
|
|
"strings"
|
|
|
|
"time"
|
2021-05-29 04:26:41 +03:00
|
|
|
|
2021-08-03 16:51:01 +03:00
|
|
|
"github.com/golang-jwt/jwt/v4"
|
|
|
|
"github.com/golang-jwt/jwt/v4/request"
|
2016-06-08 21:36:38 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
// location of the files used for signing and verification
|
|
|
|
const (
|
|
|
|
privKeyPath = "test/sample_key" // openssl genrsa -out app.rsa keysize
|
|
|
|
pubKeyPath = "test/sample_key.pub" // openssl rsa -in app.rsa -pubout > app.rsa.pub
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
verifyKey *rsa.PublicKey
|
|
|
|
signKey *rsa.PrivateKey
|
|
|
|
serverPort int
|
|
|
|
)
|
|
|
|
|
|
|
|
// read the key files before starting http handlers
|
|
|
|
func init() {
|
2022-05-28 02:11:16 +03:00
|
|
|
signBytes, err := os.ReadFile(privKeyPath)
|
2016-06-08 21:36:38 +03:00
|
|
|
fatal(err)
|
|
|
|
|
|
|
|
signKey, err = jwt.ParseRSAPrivateKeyFromPEM(signBytes)
|
|
|
|
fatal(err)
|
|
|
|
|
2022-05-28 02:11:16 +03:00
|
|
|
verifyBytes, err := os.ReadFile(pubKeyPath)
|
2016-06-08 21:36:38 +03:00
|
|
|
fatal(err)
|
|
|
|
|
|
|
|
verifyKey, err = jwt.ParseRSAPublicKeyFromPEM(verifyBytes)
|
|
|
|
fatal(err)
|
|
|
|
|
|
|
|
http.HandleFunc("/authenticate", authHandler)
|
|
|
|
http.HandleFunc("/restricted", restrictedHandler)
|
|
|
|
|
|
|
|
// Setup listener
|
|
|
|
listener, err := net.ListenTCP("tcp", &net.TCPAddr{})
|
2021-05-29 04:26:41 +03:00
|
|
|
fatal(err)
|
2016-06-08 21:36:38 +03:00
|
|
|
serverPort = listener.Addr().(*net.TCPAddr).Port
|
|
|
|
|
|
|
|
log.Println("Listening...")
|
|
|
|
go func() {
|
|
|
|
fatal(http.Serve(listener, nil))
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
func fatal(err error) {
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Define some custom types were going to use within our tokens
|
|
|
|
type CustomerInfo struct {
|
|
|
|
Name string
|
|
|
|
Kind string
|
|
|
|
}
|
|
|
|
|
|
|
|
type CustomClaimsExample struct {
|
2021-08-22 20:23:13 +03:00
|
|
|
*jwt.RegisteredClaims
|
2016-06-08 21:36:38 +03:00
|
|
|
TokenType string
|
|
|
|
CustomerInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
func Example_getTokenViaHTTP() {
|
|
|
|
// See func authHandler for an example auth handler that produces a token
|
|
|
|
res, err := http.PostForm(fmt.Sprintf("http://localhost:%v/authenticate", serverPort), url.Values{
|
|
|
|
"user": {"test"},
|
|
|
|
"pass": {"known"},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if res.StatusCode != 200 {
|
|
|
|
fmt.Println("Unexpected status code", res.StatusCode)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read the token out of the response body
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
io.Copy(buf, res.Body)
|
|
|
|
res.Body.Close()
|
|
|
|
tokenString := strings.TrimSpace(buf.String())
|
|
|
|
|
|
|
|
// Parse the token
|
|
|
|
token, err := jwt.ParseWithClaims(tokenString, &CustomClaimsExample{}, func(token *jwt.Token) (interface{}, error) {
|
|
|
|
// since we only use the one private key to sign the tokens,
|
|
|
|
// we also only use its public counter part to verify
|
|
|
|
return verifyKey, nil
|
|
|
|
})
|
|
|
|
fatal(err)
|
|
|
|
|
|
|
|
claims := token.Claims.(*CustomClaimsExample)
|
|
|
|
fmt.Println(claims.CustomerInfo.Name)
|
|
|
|
|
|
|
|
//Output: test
|
|
|
|
}
|
|
|
|
|
|
|
|
func Example_useTokenViaHTTP() {
|
|
|
|
|
|
|
|
// Make a sample token
|
|
|
|
// In a real world situation, this token will have been acquired from
|
|
|
|
// some other API call (see Example_getTokenViaHTTP)
|
|
|
|
token, err := createToken("foo")
|
|
|
|
fatal(err)
|
|
|
|
|
|
|
|
// Make request. See func restrictedHandler for example request processor
|
|
|
|
req, err := http.NewRequest("GET", fmt.Sprintf("http://localhost:%v/restricted", serverPort), nil)
|
|
|
|
fatal(err)
|
|
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", token))
|
|
|
|
res, err := http.DefaultClient.Do(req)
|
|
|
|
fatal(err)
|
|
|
|
|
|
|
|
// Read the response body
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
io.Copy(buf, res.Body)
|
|
|
|
res.Body.Close()
|
|
|
|
fmt.Println(buf.String())
|
|
|
|
|
|
|
|
// Output: Welcome, foo
|
|
|
|
}
|
|
|
|
|
|
|
|
func createToken(user string) (string, error) {
|
|
|
|
// create a signer for rsa 256
|
|
|
|
t := jwt.New(jwt.GetSigningMethod("RS256"))
|
|
|
|
|
|
|
|
// set our claims
|
|
|
|
t.Claims = &CustomClaimsExample{
|
2021-08-22 20:23:13 +03:00
|
|
|
&jwt.RegisteredClaims{
|
2016-06-08 21:36:38 +03:00
|
|
|
// set the expire time
|
2021-08-22 20:23:13 +03:00
|
|
|
// see https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4
|
|
|
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute * 1)),
|
2016-06-08 21:36:38 +03:00
|
|
|
},
|
|
|
|
"level1",
|
|
|
|
CustomerInfo{user, "human"},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Creat token string
|
|
|
|
return t.SignedString(signKey)
|
|
|
|
}
|
|
|
|
|
|
|
|
// reads the form values, checks them and creates the token
|
|
|
|
func authHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
// make sure its post
|
|
|
|
if r.Method != "POST" {
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
fmt.Fprintln(w, "No POST", r.Method)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
user := r.FormValue("user")
|
|
|
|
pass := r.FormValue("pass")
|
|
|
|
|
|
|
|
log.Printf("Authenticate: user[%s] pass[%s]\n", user, pass)
|
|
|
|
|
|
|
|
// check values
|
|
|
|
if user != "test" || pass != "known" {
|
|
|
|
w.WriteHeader(http.StatusForbidden)
|
|
|
|
fmt.Fprintln(w, "Wrong info")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
tokenString, err := createToken(user)
|
|
|
|
if err != nil {
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
fmt.Fprintln(w, "Sorry, error while Signing Token!")
|
|
|
|
log.Printf("Token Signing error: %v\n", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/jwt")
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
fmt.Fprintln(w, tokenString)
|
|
|
|
}
|
|
|
|
|
|
|
|
// only accessible with a valid token
|
|
|
|
func restrictedHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
// Get token from request
|
2021-08-03 16:51:01 +03:00
|
|
|
token, err := request.ParseFromRequest(r, request.OAuth2Extractor, func(token *jwt.Token) (interface{}, error) {
|
2016-06-08 21:36:38 +03:00
|
|
|
// since we only use the one private key to sign the tokens,
|
|
|
|
// we also only use its public counter part to verify
|
|
|
|
return verifyKey, nil
|
2021-08-03 16:51:01 +03:00
|
|
|
}, request.WithClaims(&CustomClaimsExample{}))
|
2016-06-08 21:36:38 +03:00
|
|
|
|
|
|
|
// If the token is missing or invalid, return error
|
|
|
|
if err != nil {
|
|
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
|
|
fmt.Fprintln(w, "Invalid token:", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Token is valid
|
|
|
|
fmt.Fprintln(w, "Welcome,", token.Claims.(*CustomClaimsExample).Name)
|
|
|
|
}
|