Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement RedirectCode option #56

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ If the claim is a json array of strings, the allow and deny directives will chec
jwt {
path [path]
redirect [location]
redirectcode [code]
allow [claim] [value]
deny [claim] [value]
}
Expand All @@ -51,6 +52,8 @@ The middleware will deny everyone with `role: member` but will allow the specifi

If the optional `redirect` is set, the middleware will send a redirect to the supplied location (HTTP 303) instead of an access denied code, if the access is denied.

If the optional `redirectcode` is set, the middleware will use the supplied code instead of HTTP 303

### Ways of passing a token for validation

There are three ways to pass the token for validation: (1) in the `Authorization` header, (2) as a cookie, and (3) as a URL query parameter. The middleware will **by default** look in those places in the order listed and return `401` if it can't find any token.
Expand Down
19 changes: 19 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package jwt

import (
"fmt"
"strconv"

"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
Expand Down Expand Up @@ -41,6 +42,7 @@ type Rule struct {
ExceptedPaths []string
AccessRules []AccessRule
Redirect string
RedirectCode int
AllowRoot bool
KeyBackends []KeyBackend
Passthrough bool
Expand Down Expand Up @@ -157,6 +159,16 @@ func parse(c *caddy.Controller) ([]Rule, error) {
return nil, c.ArgErr()
}
r.Redirect = args1[0]
case "redirectcode":
args1 := c.RemainingArgs()
if len(args1) != 1 {
return nil, c.ArgErr()
}
tmp, err := strconv.Atoi(args1[0])
if err != nil {
return nil, c.Err(err.Error())
}
r.RedirectCode = tmp
case "publickey":
args1 := c.RemainingArgs()
if len(args1) != 1 {
Expand Down Expand Up @@ -258,3 +270,10 @@ func parse(c *caddy.Controller) ([]Rule, error) {

return rules, nil
}

func (r Rule) GetRedirectCode(defaultCode int) int {
if r.RedirectCode > 0 {
return r.RedirectCode
}
return defaultCode
}
26 changes: 22 additions & 4 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,22 @@ var _ = Describe("JWTAuth Config", func() {
Expect(err).To(BeNil())
})

It("getting the code works as expected", func() {
tests := []struct {
input Rule
expect int
}{
{Rule{}, http.StatusSeeOther},
{Rule{RedirectCode: 307}, http.StatusTemporaryRedirect},
{Rule{RedirectCode: 0}, http.StatusSeeOther},
{Rule{RedirectCode: -5}, http.StatusSeeOther},
}

for _, test := range tests {
Expect(test.input.GetRedirectCode(http.StatusSeeOther)).To(Equal(test.expect))
}
})

It("parses simple and complex blocks", func() {
tests := []struct {
input string
Expand All @@ -45,12 +61,14 @@ var _ = Describe("JWTAuth Config", func() {
{"jwt {\npath /test\n}", false, []Rule{{Path: "/test"}}},
{`jwt {
path /test
redirect /login
redirect /login
redirectcode 307
allow user test
}`, false, []Rule{{
Path: "/test",
Redirect: "/login",
AccessRules: []AccessRule{{ALLOW, "user", "test"}}},
Path: "/test",
Redirect: "/login",
RedirectCode: 307,
AccessRules: []AccessRule{{ALLOW, "user", "test"}}},
}},
{`jwt /test {
allow user test
Expand Down
10 changes: 6 additions & 4 deletions jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,9 @@ func ValidateToken(uToken string, keyBackend KeyBackend) (*jwt.Token, error) {
func handleUnauthorized(w http.ResponseWriter, r *http.Request, rule Rule, realm string) int {
if rule.Redirect != "" {
replacer := httpserver.NewReplacer(r, nil, "")
http.Redirect(w, r, replacer.Replace(rule.Redirect), http.StatusSeeOther)
return http.StatusSeeOther
redirectCode := rule.GetRedirectCode(http.StatusSeeOther)
http.Redirect(w, r, replacer.Replace(rule.Redirect), redirectCode)
return redirectCode
}

w.Header().Add("WWW-Authenticate", fmt.Sprintf("Bearer realm=\"%s\",error=\"invalid_token\"", realm))
Expand All @@ -286,8 +287,9 @@ func handleUnauthorized(w http.ResponseWriter, r *http.Request, rule Rule, realm
func handleForbidden(w http.ResponseWriter, r *http.Request, rule Rule, realm string) int {
if rule.Redirect != "" {
replacer := httpserver.NewReplacer(r, nil, "")
http.Redirect(w, r, replacer.Replace(rule.Redirect), http.StatusSeeOther)
return http.StatusSeeOther
redirectCode := rule.GetRedirectCode(http.StatusSeeOther)
http.Redirect(w, r, replacer.Replace(rule.Redirect), redirectCode)
return redirectCode
}
w.Header().Add("WWW-Authenticate", fmt.Sprintf("Bearer realm=\"%s\",error=\"insufficient_scope\"", realm))
return http.StatusForbidden
Expand Down
36 changes: 35 additions & 1 deletion jwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import (

"io/ioutil"

jwt "github.com/dgrijalva/jwt-go"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
jwt "github.com/dgrijalva/jwt-go"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
Expand Down Expand Up @@ -489,6 +489,40 @@ var _ = Describe("Auth", func() {
Expect(rec.Result().Header.Get("Location")).To(Equal("/login?backTo=/testing"))
})
})
Describe("Redirect with given code on access deny works", func() {
It("return 307 when a redirect is configured and access denied", func() {
req, err := http.NewRequest("GET", "/testing", nil)

rec := httptest.NewRecorder()
rw := Auth{
Rules: []Rule{{Path: "/testing", Redirect: "/login", RedirectCode: http.StatusTemporaryRedirect}},
}
result, err := rw.ServeHTTP(rec, req)
if err != nil {
Fail(fmt.Sprintf("unexpected error constructing server: %s", err))
}

Expect(result).To(Equal(http.StatusTemporaryRedirect))
Expect(rec.Result().StatusCode).To(Equal(http.StatusTemporaryRedirect))
Expect(rec.Result().Header.Get("Location")).To(Equal("/login"))
})
It("variables in location value are replaced", func() {
req, err := http.NewRequest("GET", "/testing", nil)

rec := httptest.NewRecorder()
rw := Auth{
Rules: []Rule{{Path: "/testing", Redirect: "/login?backTo={rewrite_uri}", RedirectCode: http.StatusTemporaryRedirect}},
}
result, err := rw.ServeHTTP(rec, req)
if err != nil {
Fail(fmt.Sprintf("unexpected error constructing server: %s", err))
}

Expect(result).To(Equal(http.StatusTemporaryRedirect))
Expect(rec.Result().StatusCode).To(Equal(http.StatusTemporaryRedirect))
Expect(rec.Result().Header.Get("Location")).To(Equal("/login?backTo=/testing"))
})
})
Describe("Function correctly as an authorization middleware for malformed paths", func() {
It("return 401 when no authorization header and the path is protected (malformed path - 1st level)", func() {
rw := Auth{
Expand Down