Skip to content

Instantly share code, notes, and snippets.

@henkman
Last active October 20, 2020 16:42
Show Gist options
  • Save henkman/0a7b699cf5ad8781bed985c9b458b50e to your computer and use it in GitHub Desktop.
Save henkman/0a7b699cf5ad8781bed985c9b458b50e to your computer and use it in GitHub Desktop.
webapp example with fiber + jwt-go +gorm + scrypt
package main
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"fmt"
"io"
"regexp"
"time"
"golang.org/x/crypto/scrypt"
"github.com/dgrijalva/jwt-go"
"github.com/gofiber/fiber"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
)
type Claims struct {
jwt.StandardClaims
Special string `json:"spc,omitempty"`
}
type User struct {
ID uint `gorm:"primary_key"`
Name string
Pass []byte
Special string
}
func main() {
key, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
if err != nil {
panic(err)
}
var pwsalt [32]byte
if _, err := rand.Read(pwsalt[:]); err != nil {
panic(err)
}
db, err := gorm.Open("sqlite3", ":memory:")
if err != nil {
panic(err)
}
defer db.Close()
{
db.LogMode(true)
db.AutoMigrate(&User{}) // creates tables
hash, _ := pwhash([]byte("noodle"), pwsalt[:])
db.Create(&User{Name: "dude", Pass: hash, Special: ":wowi:"})
}
app := fiber.New()
api := app.Group("/api")
{
// curl -v localhost:3000/api/login -H "Content-Type: application/json" -d '{"name":"dude","pass":"noodle"}'
// curl -v localhost:3000/api/login -F name=dude -F pass=noodle
api.Post("/login", loginHandler(key, pwsalt[:], db))
secure := api.Use(requireJWT(&key.PublicKey))
{
// curl -v localhost:3000/api -H "Authorization: Bearer ..."
secure.Get("/", func(c *fiber.Ctx) {
claims := c.Locals("claims").(*Claims)
c.SendString("Hello, " + claims.Subject)
})
// curl -v localhost:3000/api/claims -H "Authorization: Bearer ..."
secure.Get("/claims", func(c *fiber.Ctx) {
claims := c.Locals("claims").(*Claims)
tbuf := bytes.NewBufferString(fmt.Sprintf("%+v", claims))
bw := c.Fasthttp.Response.BodyWriter()
// streaming works!
io.Copy(bw, tbuf)
})
}
}
if err := app.Listen(3000); err != nil {
panic(err)
}
}
func pwhash(pw, salt []byte) ([]byte, error) {
return scrypt.Key(pw, salt, 32768, 8, 1, 32)
}
func requireJWT(key *ecdsa.PublicKey) func(*fiber.Ctx) {
reBearer := regexp.MustCompile("(?i)^Bearer ")
return func(c *fiber.Ctx) {
ts := c.Get("Authorization")
if !reBearer.MatchString(ts) {
c.Status(403).SendString("no bearer")
return
}
token, err := jwt.ParseWithClaims(ts[len("Bearer "):], &Claims{},
func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodECDSA); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v",
token.Header["alg"])
}
return key, nil
})
if err != nil {
fmt.Println("invalid token:", err)
c.Status(403).SendString("invalid token")
return
}
claims, ok := token.Claims.(*Claims)
if !ok || !token.Valid {
c.Status(403).SendString("claims invalid")
return
}
c.Locals("claims", claims)
c.Next()
}
}
func loginHandler(key *ecdsa.PrivateKey, pwsalt []byte, db *gorm.DB) func(*fiber.Ctx) {
type LoginUser struct {
Name string `json:"name" form:"name"`
Pass string `json:"pass" form:"pass"`
}
return func(c *fiber.Ctx) {
var login LoginUser
if err := c.BodyParser(&login); err != nil {
c.Status(400)
return
}
var user User
hash, _ := pwhash([]byte(login.Pass), pwsalt)
if err := db.Select("name, special").Take(&user, "name=? AND pass=?", login.Name, hash).Error; err != nil {
c.Status(403)
return
}
token := jwt.NewWithClaims(jwt.SigningMethodES512, jwt.MapClaims{
"sub": user.Name,
"iat": time.Now().Unix(),
"spc": user.Special,
})
ts, err := token.SignedString(key)
if err != nil {
fmt.Println(err)
c.Status(500)
return
}
c.Set("Authorization", "Bearer "+ts)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment