Skip to content

Instantly share code, notes, and snippets.

@abourget
Last active March 12, 2016 20:01
Show Gist options
  • Save abourget/444318d4f58f38c93460 to your computer and use it in GitHub Desktop.
Save abourget/444318d4f58f38c93460 to your computer and use it in GitHub Desktop.
Sample security middleware for goa's Security framework..
...
func serve() error {
service := goa.New("Featurette")
publicKeys := loadJWTPublicKeys(service)
...
// JWTSecurity was generated, because I named my security method "jwt"
app.JWTSecurity.Use(securityMiddleware(publicKeys))
return service.ListenAndServe("...")
}
package main
import (
"crypto/rsa"
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
jwt "github.com/dgrijalva/jwt-go"
"github.com/goadesign/goa"
"github.com/spf13/viper"
"golang.org/x/net/context"
)
func loadJWTPublicKeys(service *goa.Service) (out []*rsa.PublicKey) {
configs := []string{"JWT_PUBKEY1", "JWT_PUBKEY2", "JWT_PUBKEY3"}
for _, configKey := range configs {
pem := strings.Replace(viper.GetString(configKey), "\\n", "\n", -1)
if pem == "" {
continue
}
//fmt.Println("PEM:", pem)
key, err := jwt.ParseRSAPublicKeyFromPEM([]byte(pem))
if err != nil {
goa.Error(nil, fmt.Sprintf("error loading key %q: %s", configKey, err))
continue
}
service.Info("loaded PEM key", "env_var", fmt.Sprintf("FEATURETTE_%s", configKey))
out = append(out, key)
}
if len(out) == 0 {
service.Error("couldn't load any signing JWT_PUBKEYs")
os.Exit(1)
}
return
}
func securityMiddleware(publicKeys []*rsa.PublicKey) goa.Middleware {
return func(h goa.Handler) goa.Handler {
return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
method := goa.SecurityMethod(ctx).(*goa.APIKeySecurity)
// optional check; you defined the design, you can assume it's
// always "header".
if method.In != "header" {
return fmt.Errorf("whoops, method %q with in = %q not supported", method.Name, method.In)
}
val := req.Header.Get(method.Name)
if val == "" {
goa.Response(ctx).WriteHeader(401)
return fmt.Errorf("missing header %q", method.Name)
}
if !strings.HasPrefix(strings.ToLower(val), "bearer ") {
goa.Response(ctx).WriteHeader(401)
return fmt.Errorf("invalid or malformed %q header, expected 'Authorization: Bearer JWT-token...'", val)
}
incomingToken := strings.Split(val, " ")[1]
token, err := validateTokenWithKeys(incomingToken, publicKeys)
if err != nil {
goa.Info(ctx, "JWT token validation failed", "err", err)
w := goa.Response(ctx)
w.WriteHeader(401)
json.NewEncoder(w).Encode(map[string]interface{}{
"error": "jwt_invalid",
"message": "JWT validation failed",
})
return nil
}
var claimedScopes = make(map[string]bool)
if token.Claims["scopes"] != nil {
scopes, _ := token.Claims["scopes"].(string)
for _, scope := range strings.Split(scopes, ",") {
claimedScopes[scope] = true
}
}
requiredScopes := goa.Scopes(ctx)
for _, scope := range requiredScopes {
if !claimedScopes[scope] {
goa.Info(ctx, "missing required scope in JWT token", "scope", scope)
w := goa.Response(ctx)
w.WriteHeader(401)
json.NewEncoder(w).Encode(map[string]interface{}{
"error": "scope_not_present",
"message": fmt.Sprintf("Required scope %q not present in JWT claims", scope),
})
return nil
}
}
return h(context.WithValue(ctx, jwtKey, token), rw, req)
}
}
}
// validateTokenWithKeys parses the JWT token with multiple keys, and returns
// the first that is valid. This is to allow key rotation of signing authority
// without disrupting current keys, and letting their expiry take effect.
func validateTokenWithKeys(incomingToken string, keys []*rsa.PublicKey) (token *jwt.Token, err error) {
for _, pubkey := range keys {
token, err = jwt.Parse(incomingToken, func(token *jwt.Token) (interface{}, error) {
if token.Method.Alg() != "RS256" {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return pubkey, nil
})
if err == nil {
return
}
}
return
}
const (
jwtKey contextKey = iota + 1
)
type contextKey int
// JWT retrieves the JWT token from a `context` that went through our security
// middleware.
func JWT(ctx context.Context) *jwt.Token {
token, ok := ctx.Value(jwtKey).(*jwt.Token)
if !ok {
return nil
}
return token
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment