Added the option of validating JWT on user selected http handlers.
diff --git a/oauth.go b/oauth.go new file mode 100644 index 0000000..c55beb7 --- /dev/null +++ b/oauth.go
@@ -0,0 +1,144 @@ +package goscaffold + +import ( + "context" + "encoding/json" + "github.com/SermoDigital/jose/crypto" + "github.com/SermoDigital/jose/jws" + "github.com/SermoDigital/jose/jwt" + "github.com/julienschmidt/httprouter" + "github.com/justinas/alice" + "net/http" +) + +const params = "params" + +/* Errors to return */ +type Errors []string + +/* +The SSO key parameters +*/ +type ssoKey struct { + Alg string `json:"alg"` + Value string `json:"value"` + Kty string `json:"kty"` + Use string `json:"use"` + N string `json:"n"` + E string `json:"e"` +} + +/* +OAuth structure that provides http connection to the URL that has the public +key for verifying the JWT token +*/ +type OAuth struct { + keyURL string + client *http.Client +} + +/* +The interface functions offered to clients that act on OAuth param, +used to verify JWT tokens for the Http handler functions client +wishes to validate against (via SSOHandler). +*/ +type OAuthService interface { + SSOHandler(p string, h func(http.ResponseWriter, *http.Request)) (string, httprouter.Handle) +} + +/* +SetParamsInRequest Sets the params and its values in the request +*/ +func SetParamsInRequest(r *http.Request, ps httprouter.Params) *http.Request { + newContext := context.WithValue(r.Context(), params, ps) + return r.WithContext(newContext) +} + +/* +FetchParams fetches the param values, given the params in the request +*/ +func FetchParams(r *http.Request) httprouter.Params { + ctx := r.Context() + return ctx.Value(params).(httprouter.Params) +} + +/* +SSOHandler offers the users the flexibility of choosing which http handlers +need JWT validation. +*/ +func (a *OAuth) SSOHandler(p string, h func(http.ResponseWriter, *http.Request)) (string, httprouter.Handle) { + return p, a.VerifyOAuth(alice.New().ThenFunc(h)) +} + +/* +VerifyOAuth verifies the JWT token in the request using the public key configured +via CreateOAuth constructor. +*/ +func (a *OAuth) VerifyOAuth(next http.Handler) httprouter.Handle { + + return func(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) { + + jwt, err := jws.ParseJWTFromRequest(r) + if err != nil { + WriteErrorResponse(http.StatusBadRequest, err.Error(), rw) + return + } + + /* Validate the JWT */ + err = a.Validate(jwt) + if err != nil { + WriteErrorResponse(http.StatusBadRequest, err.Error(), rw) + return + } + + /* Set the params in the request */ + r = SetParamsInRequest(r, ps) + next.ServeHTTP(rw, r) + } + +} + +/* +ValidateKey validate the jwt and return an error if it fails +*/ +func (a *OAuth) Validate(jwt jwt.JWT) error { + + r, err := a.client.Get(a.keyURL) + + if err != nil { + return err + } + + defer r.Body.Close() + ssoKey := &ssoKey{} + err = json.NewDecoder(r.Body).Decode(ssoKey) + if err != nil { + return err + } + + /* Retrieve the Public Key */ + publieKey, err := crypto.ParseRSAPublicKeyFromPEM([]byte(ssoKey.Value)) + if err != nil { + return err + } + + /* Return the status of validation */ + return jwt.Validate(publieKey, crypto.SigningMethodRS256) +} + +/* +WriteErrorResponse write a non 200 error response +*/ +func WriteErrorResponse(statusCode int, message string, w http.ResponseWriter) { + errors := Errors{message} + WriteErrorResponses(statusCode, errors, w) +} + +/* +WriteErrorResponses write our error responses +*/ +func WriteErrorResponses(statusCode int, errors Errors, w http.ResponseWriter) { + w.WriteHeader(statusCode) + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(errors) +}