Skip to content

Commit

Permalink
feat: add url_param config option to redirect error handler. (#520)
Browse files Browse the repository at this point in the history
This change introduces a url_param config option for redirect error handler.
If it contains a url paramter name, the redirect url will have this parameter
set, containing the current url (from which Oathkeeper has redirected the user).

This can be useful in passing the return_to url to Kratos, so user can be
redirected to the page they initially wanted to access after a successfull sign in.

Closes #511
  • Loading branch information
aeneasr authored Sep 21, 2020
1 parent cd04902 commit b5bb3bc
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 3 deletions.
7 changes: 7 additions & 0 deletions .schema/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,13 @@
],
"default": 302
},
"return_to_query_param": {
"title": "URL query parameter",
"description": "Adds the original URL the request tried to access to the query parameter.",
"type": "string",
"pattern": "^[A-Za-z0-9,._~-]*$",
"default": ""
},
"when": {
"$ref": "#/definitions/configErrorsWhen"
}
Expand Down
5 changes: 5 additions & 0 deletions docs/docs/pipeline/error.md
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,8 @@ The `redirect` Error Handler returns a HTTP 302/301 response with a `Location`
Header. As discussed in the previous section, you can define error matching
conditions under the `when` key.

If you want to append the current url (where the error happened) to address redirected to, You can specify `return_to_query_param` to set the name of parameter that will hold the url.

**Example**

```json5
Expand All @@ -421,6 +423,7 @@ conditions under the `when` key.
handler: 'json',
config: {
to: 'http://my-website/login', // required!!
return_to_query_param: 'return_to',
code: 301, // defaults to 302 - only 301 and 302 are supported.
when: [
// ...
Expand All @@ -429,6 +432,8 @@ conditions under the `when` key.
}
```

When the user accesses a protected url `http://my-website/settings`, they will be redirected to `http://my-website/login?return_to=http%3A%2F%2Fmy-website%2Fsettings`. The login page can use the `return_to` paramter to return user to intended page after a successful login.

### `www_authenticate`

The `www_authenticate` Error Handler responds with HTTP 401 and a
Expand Down
23 changes: 20 additions & 3 deletions pipeline/errors/error_redirect.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package errors
import (
"encoding/json"
"net/http"
"net/url"

"github.com/ory/oathkeeper/driver/configuration"
"github.com/ory/oathkeeper/pipeline"
Expand All @@ -13,8 +14,9 @@ var _ Handler = new(ErrorRedirect)

type (
ErrorRedirectConfig struct {
To string `json:"to"`
Code int `json:"code"`
To string `json:"to"`
Code int `json:"code"`
ReturnToQueryParam string `json:"return_to_query_param"`
}
ErrorRedirect struct {
c configuration.Provider
Expand All @@ -38,7 +40,7 @@ func (a *ErrorRedirect) Handle(w http.ResponseWriter, r *http.Request, config js
return err
}

http.Redirect(w, r, c.To, c.Code)
http.Redirect(w, r, a.RedirectURL(r, c), c.Code)
return nil
}

Expand Down Expand Up @@ -66,3 +68,18 @@ func (a *ErrorRedirect) Config(config json.RawMessage) (*ErrorRedirectConfig, er
func (a *ErrorRedirect) GetID() string {
return "redirect"
}

func (a *ErrorRedirect) RedirectURL(r *http.Request, c *ErrorRedirectConfig) string {
if c.ReturnToQueryParam == "" {
return c.To
}

u, err := url.Parse(c.To)
if err != nil {
return c.To
}
q := u.Query()
q.Set(c.ReturnToQueryParam, r.URL.String())
u.RawQuery = q.Encode()
return u.String()
}
12 changes: 12 additions & 0 deletions pipeline/errors/error_redirect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -86,6 +87,17 @@ func TestErrorRedirect(t *testing.T) {
assert.Equal(t, "/test", rw.Header().Get("Location"))
},
},
{
d: "should redirect with return_to param",
givenError: &herodot.ErrNotFound,
config: `{"to":"http://test/signin","return_to_query_param":"return_to"}`,
assert: func(t *testing.T, rw *httptest.ResponseRecorder) {
assert.Equal(t, 302, rw.Code)
location, err := url.Parse(rw.Header().Get("Location"))
require.NoError(t, err)
assert.Equal(t, "/test", location.Query().Get("return_to"))
},
},
} {
t.Run(fmt.Sprintf("case=%d/description=%s", k, tc.d), func(t *testing.T) {
w := httptest.NewRecorder()
Expand Down

0 comments on commit b5bb3bc

Please sign in to comment.