diff --git a/docs/reference/filters.md b/docs/reference/filters.md
index cf702a2784..d83bb2d088 100644
--- a/docs/reference/filters.md
+++ b/docs/reference/filters.md
@@ -1643,6 +1643,11 @@ you pass the `-enable-oauth2-grant-flow` flag.
The filter may be used with the [grantClaimsQuery](#grantclaimsquery) filter to perform
authz and access control.
+The filter also supports javascript login redirect stub that can be used e.g. to store location hash.
+To enable the stub, add preceding [annotate](#annotate) filter with `oauthGrant.loginRedirectStub` key and
+HTML content that will be served to the client instead of `307 Temporary Redirect` to the authorization URL.
+The filter will replace `{{authCodeURL}}` placeholder in the content with the actual authorization URL.
+
See the [tutorial](../tutorials/auth.md#oauth2-authorization-grant-flow) for step-by-step
instructions.
@@ -1655,6 +1660,27 @@ all:
-> "http://localhost:9090";
```
+```
+single_page_app:
+ *
+ -> annotate("oauthGrant.loginRedirectStub", `
+
+
+
+ Redirecting...
+
+
+
+ `)
+ -> oauthGrant()
+ -> "http://localhost:9090";
+```
+
Skipper arguments:
| Argument | Required? | Description |
diff --git a/filters/auth/grant.go b/filters/auth/grant.go
index 577f385940..43cac8c4ef 100644
--- a/filters/auth/grant.go
+++ b/filters/auth/grant.go
@@ -4,10 +4,14 @@ import (
"context"
"errors"
"fmt"
+ "io"
"net/http"
+ "strconv"
+ "strings"
"time"
"github.com/zalando/skipper/filters"
+ "github.com/zalando/skipper/filters/annotate"
"golang.org/x/oauth2"
)
@@ -84,12 +88,25 @@ func loginRedirectWithOverride(ctx filters.FilterContext, config *OAuthConfig, o
return
}
- ctx.Serve(&http.Response{
- StatusCode: http.StatusTemporaryRedirect,
- Header: http.Header{
- "Location": []string{authConfig.AuthCodeURL(state, config.GetAuthURLParameters(redirect)...)},
- },
- })
+ authCodeURL := authConfig.AuthCodeURL(state, config.GetAuthURLParameters(redirect)...)
+
+ if lrs, ok := annotate.GetAnnotations(ctx)["oauthGrant.loginRedirectStub"]; ok {
+ lrs = strings.ReplaceAll(lrs, "{{authCodeURL}}", authCodeURL)
+ ctx.Serve(&http.Response{
+ StatusCode: http.StatusOK,
+ Header: http.Header{
+ "Content-Length": []string{strconv.Itoa(len(lrs))},
+ },
+ Body: io.NopCloser(strings.NewReader(lrs)),
+ })
+ } else {
+ ctx.Serve(&http.Response{
+ StatusCode: http.StatusTemporaryRedirect,
+ Header: http.Header{
+ "Location": []string{authCodeURL},
+ },
+ })
+ }
}
func (f *grantFilter) refreshToken(token *oauth2.Token, req *http.Request) (*oauth2.Token, error) {
diff --git a/filters/auth/grant_test.go b/filters/auth/grant_test.go
index 1d7bda2276..d0d10a4e8e 100644
--- a/filters/auth/grant_test.go
+++ b/filters/auth/grant_test.go
@@ -4,6 +4,7 @@ import (
"context"
"crypto/tls"
"encoding/json"
+ "fmt"
"net"
"net/http"
"net/http/cookiejar"
@@ -1000,3 +1001,32 @@ func TestGrantInsecure(t *testing.T) {
}
}
}
+
+func TestGrantLoginRedirectStub(t *testing.T) {
+ provider := newGrantTestAuthServer(testToken, testAccessCode)
+ defer provider.Close()
+
+ tokeninfo := newGrantTestTokeninfo(testToken, "")
+ defer tokeninfo.Close()
+
+ config := newGrantTestConfig(tokeninfo.URL, provider.URL)
+
+ const stubContent = "stub content"
+
+ routes := eskip.MustParse(fmt.Sprintf(`*
+ -> annotate("oauthGrant.loginRedirectStub", "%s")
+ -> oauthGrant()
+ -> status(204)
+ ->
+ `, stubContent))
+
+ proxy, client := newAuthProxy(t, config, routes)
+ defer proxy.Close()
+
+ rsp, body, err := client.GetBody(proxy.URL + "/test")
+ require.NoError(t, err)
+
+ assert.Equal(t, rsp.StatusCode, http.StatusOK)
+ assert.Equal(t, int64(len(stubContent)), rsp.ContentLength)
+ assert.Equal(t, stubContent, string(body))
+}