Skip to content

Commit

Permalink
feat: support and document api flow in session issuer hook
Browse files Browse the repository at this point in the history
  • Loading branch information
aeneasr committed Aug 25, 2020
1 parent 421320c commit 91f3cc7
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 31 deletions.
40 changes: 38 additions & 2 deletions docs/docs/self-service/hooks/index.mdx
Expand Up @@ -75,8 +75,7 @@ response.

:::

It sends a `Set-Cookie` header which contains the session
cookie. To use it, you must first define one or more (for secret rotation)
To use this hook, you must first define one or more (for secret rotation)
secrets. You can either choose to use the "default" secrets or the more specific "cookie" secrets.
The other required config is setting the hook in `after` work flows:

Expand All @@ -100,6 +99,43 @@ selfservice:
# can not be configured
```

Depending on the registration flow type the behavior changes.

### Registration Flow via Browser

When performing a registration flow with a Browser, this hook sends a
`Set-Cookie` HTTP header which contains the session cookie.

Therefore, the user is logged in immediately.

### Registration Flow via API

When performing a registration flow with an API client (e.g. mobile apo), this hook
creates a session and returns the session token and the session itself in the
response body as application/json:

```json
{
"session": {
"id": "..."
// ...
},
"session_token": "...",
"identity": {
"id": "..."
// ...
}
}
```

:::info

Because the HTTP reply is handled by the hook itself, no other hooks can be executed because the HTTP
reply can not be modified further (e.g. HTTP Status Code was already sent as 200 and cannot be changed to 301).
You must ensure that the session hook is the last hook in your configuration!

:::

## Settings

Hooks running after successfully updating user settings and are defined per
Expand Down
20 changes: 12 additions & 8 deletions selfservice/hook/session_issuer.go
Expand Up @@ -4,20 +4,23 @@ import (
"net/http"
"time"

"github.com/ory/kratos/selfservice/flow/login"
"github.com/pkg/errors"

"github.com/ory/kratos/selfservice/flow"
"github.com/ory/kratos/selfservice/flow/registration"
"github.com/ory/kratos/session"
"github.com/ory/kratos/x"
)

var (
_ login.PostHookExecutor = new(SessionIssuer)
_ registration.PostHookPostPersistExecutor = new(SessionIssuer)
)

type (
sessionIssuerDependencies interface {
session.ManagementProvider
session.PersistenceProvider
x.WriterProvider
}
SessionIssuerProvider interface {
HookSessionIssuer() *SessionIssuer
Expand All @@ -36,13 +39,14 @@ func (e *SessionIssuer) ExecutePostRegistrationPostPersistHook(w http.ResponseWr
if err := e.r.SessionPersister().CreateSession(r.Context(), s); err != nil {
return err
}
return e.r.SessionManager().IssueCookie(r.Context(), w, r, s)
}

func (e *SessionIssuer) ExecuteLoginPostHook(w http.ResponseWriter, r *http.Request, a *login.Flow, s *session.Session) error {
s.AuthenticatedAt = time.Now().UTC()
if err := e.r.SessionPersister().CreateSession(r.Context(), s); err != nil {
return err
if a.Type == flow.TypeAPI {
e.r.Writer().Write(w, r, &registration.APIFlowResponse{
Session: s, Token: s.Token,
Identity: s.Identity,
})
return errors.WithStack(registration.ErrHookAbortFlow)
}

return e.r.SessionManager().IssueCookie(r.Context(), w, r, s)
}
59 changes: 38 additions & 21 deletions selfservice/hook/session_issuer_test.go
Expand Up @@ -2,19 +2,23 @@ package hook_test

import (
"context"
"errors"
"net/http"
"net/http/httptest"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tidwall/gjson"

"github.com/ory/viper"

"github.com/ory/kratos/driver/configuration"
"github.com/ory/kratos/identity"
"github.com/ory/kratos/internal"
"github.com/ory/kratos/selfservice/flow"
"github.com/ory/kratos/selfservice/flow/registration"
"github.com/ory/kratos/selfservice/hook"
"github.com/ory/kratos/session"
"github.com/ory/kratos/x"
Expand All @@ -28,31 +32,44 @@ func TestSessionIssuer(t *testing.T) {
var r http.Request
h := hook.NewSessionIssuer(reg)

t.Run("method=sign-in", func(t *testing.T) {
w := httptest.NewRecorder()
sid := x.NewUUID()
t.Run("method=sign-up", func(t *testing.T) {
t.Run("flow=browser", func(t *testing.T) {
w := httptest.NewRecorder()
sid := x.NewUUID()

i := identity.NewIdentity(configuration.DefaultIdentityTraitsSchemaID)
require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i))
require.NoError(t, h.ExecuteLoginPostHook(w, &r, nil, &session.Session{ID: sid, Identity: i}))
i := identity.NewIdentity(configuration.DefaultIdentityTraitsSchemaID)
require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i))
require.NoError(t, h.ExecutePostRegistrationPostPersistHook(w, &r,
&registration.Flow{Type: flow.TypeBrowser}, &session.Session{ID: sid, Identity: i}))

got, err := reg.SessionPersister().GetSession(context.Background(), sid)
require.NoError(t, err)
assert.Equal(t, sid, got.ID)
assert.True(t, got.AuthenticatedAt.After(time.Now().Add(-time.Minute)))
})
got, err := reg.SessionPersister().GetSession(context.Background(), sid)
require.NoError(t, err)
assert.Equal(t, sid, got.ID)
assert.True(t, got.AuthenticatedAt.After(time.Now().Add(-time.Minute)))

t.Run("method=sign-up", func(t *testing.T) {
w := httptest.NewRecorder()
sid := x.NewUUID()
assert.Contains(t, w.Header().Get("Set-Cookie"), session.DefaultSessionCookieName)
})

t.Run("flow=api", func(t *testing.T) {
w := httptest.NewRecorder()

i := identity.NewIdentity(configuration.DefaultIdentityTraitsSchemaID)
s := &session.Session{ID: x.NewUUID(), Identity: i}
f := &registration.Flow{Type: flow.TypeAPI}

require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i))
require.True(t, errors.Is(h.ExecutePostRegistrationPostPersistHook(w, &r, f, s), registration.ErrHookAbortFlow))

i := identity.NewIdentity(configuration.DefaultIdentityTraitsSchemaID)
require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), i))
require.NoError(t, h.ExecutePostRegistrationPostPersistHook(w, &r, nil, &session.Session{ID: sid, Identity: i}))
got, err := reg.SessionPersister().GetSession(context.Background(), s.ID)
require.NoError(t, err)
assert.Equal(t, s.ID, got.ID)
assert.True(t, got.AuthenticatedAt.After(time.Now().Add(-time.Minute)))

got, err := reg.SessionPersister().GetSession(context.Background(), sid)
require.NoError(t, err)
assert.Equal(t, sid, got.ID)
assert.True(t, got.AuthenticatedAt.After(time.Now().Add(-time.Minute)))
assert.Empty(t, w.Header().Get("Set-Cookie"))
body := w.Body.Bytes()
assert.Equal(t, i.ID, gjson.GetBytes(body, "identity.id").String())
assert.Equal(t, s.ID, gjson.GetBytes(body, "session.id").String())
assert.Equal(t, got.Token, gjson.GetBytes(body, "session_token").String())
})
})
}

0 comments on commit 91f3cc7

Please sign in to comment.