Skip to content

Commit

Permalink
feat: oidc api flow initial commit
Browse files Browse the repository at this point in the history
styles: format code

feat: add registration api flow

chore: mostly renames

styles: format code

test: audience check

feat: webview support

feat: webview support - added session_token to redirect URL query params

feat: webview support - errors handling

fix(oidc api webview): check flow messages are set
  • Loading branch information
splaunov committed Dec 27, 2022
1 parent 1ed6839 commit 3f5b41c
Show file tree
Hide file tree
Showing 36 changed files with 1,113 additions and 212 deletions.
1 change: 1 addition & 0 deletions cmd/clidoc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ func init() {
"NewInfoSelfServiceContinueLoginWebAuthn": text.NewInfoSelfServiceContinueLoginWebAuthn(),
"NewInfoSelfServiceLoginContinue": text.NewInfoSelfServiceLoginContinue(),
"NewErrorValidationSuchNoWebAuthnUser": text.NewErrorValidationSuchNoWebAuthnUser(),
"NewErrorValidationOIDCUserNotFound": text.NewErrorValidationOIDCUserNotFound(),
}
}

Expand Down
5 changes: 5 additions & 0 deletions driver/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ const (
ViperKeySelfServiceStrategyConfig = "selfservice.methods"
ViperKeySelfServiceBrowserDefaultReturnTo = "selfservice." + DefaultBrowserReturnURL
ViperKeyURLsAllowedReturnToDomains = "selfservice.allowed_return_urls"
ViperKeySelfServiceWebViewRedirectURL = "selfservice.webview_redirect_uri"
ViperKeySelfServiceRegistrationEnabled = "selfservice.flows.registration.enabled"
ViperKeySelfServiceRegistrationUI = "selfservice.flows.registration.ui_url"
ViperKeySelfServiceRegistrationRequestLifespan = "selfservice.flows.registration.lifespan"
Expand Down Expand Up @@ -804,6 +805,10 @@ func (p *Config) SelfServiceBrowserDefaultReturnTo(ctx context.Context) *url.URL
return p.ParseAbsoluteOrRelativeURIOrFail(ctx, ViperKeySelfServiceBrowserDefaultReturnTo)
}

func (p *Config) SelfServiceWebViewRedirectURL(ctx context.Context) *url.URL {
return p.GetProvider(ctx).URIF(ViperKeySelfServiceWebViewRedirectURL, nil)
}

func (p *Config) guessBaseURL(ctx context.Context, keyHost, keyPort string, defaultPort int) *url.URL {
port := p.GetProvider(ctx).IntF(keyPort, defaultPort)

Expand Down
24 changes: 24 additions & 0 deletions embedx/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,21 @@
},
"requested_claims": {
"$ref": "#/definitions/OIDCClaims"
},
"allowed_audiences": {
"title": "List of values to check audience field of ID Token",
"description": "The audience field of ID Token should be equal to one of the items in this list",
"type": "array",
"items": {
"type": "string"
},
"examples": [
[
"com.my-app.app",
"com.my-app.app.bundle.id"
]
],
"uniqueItems": true
}
},
"additionalProperties": false,
Expand Down Expand Up @@ -1013,6 +1028,15 @@
"default_browser_return_url": {
"$ref": "#/definitions/defaultReturnTo"
},
"webview_redirect_uri": {
"type": "string",
"title": "Final webview redirect URI",
"description": "Mobile app should detect this webview redirect and read a session token from the response body.",
"format": "uri",
"examples": [
"https://auth.myexample.org/oidc/success"
]
},
"allowed_return_urls": {
"title": "Allowed Return To URLs",
"description": "List of URLs that are allowed to be redirected to. A redirection request is made by appending `?return_to=...` to Login, Registration, and other self-service flows.",
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ require (
github.com/bwmarrin/discordgo v0.23.0
github.com/bxcodec/faker/v3 v3.3.1
github.com/cenkalti/backoff v2.2.1+incompatible
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/coreos/go-oidc/v3 v3.1.0
github.com/cortesi/modd v0.0.0-20210323234521-b35eddab86cc
github.com/davecgh/go-spew v1.1.1
github.com/davidrjonas/semver-cli v0.0.0-20190116233701-ee19a9a0dda6
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,8 @@ github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-oidc/v3 v3.1.0 h1:6avEvcdvTa1qYsOZ6I5PRkSYHzpTNWgKYmaJfaYbrRw=
github.com/coreos/go-oidc/v3 v3.1.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
Expand Down Expand Up @@ -1678,6 +1680,7 @@ golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
Expand Down
5 changes: 3 additions & 2 deletions internal/testhelpers/selfservice_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ func SubmitLoginForm(
forced bool,
expectedStatusCode int,
expectedURL string,
opts ...InitFlowWithOption,
) string {
if hc == nil {
hc = new(http.Client)
Expand All @@ -217,9 +218,9 @@ func SubmitLoginForm(
hc.Transport = NewTransportWithLogger(hc.Transport, t)
var f *kratos.LoginFlow
if isAPI {
f = InitializeLoginFlowViaAPI(t, hc, publicTS, forced)
f = InitializeLoginFlowViaAPI(t, hc, publicTS, forced, opts...)
} else {
f = InitializeLoginFlowViaBrowser(t, hc, publicTS, forced, isSPA, false, false)
f = InitializeLoginFlowViaBrowser(t, hc, publicTS, forced, isSPA, false, false, opts...)
}

time.Sleep(time.Millisecond) // add a bit of delay to allow `1ns` to time out.
Expand Down
3 changes: 2 additions & 1 deletion internal/testhelpers/selfservice_registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ func SubmitRegistrationForm(
isSPA bool,
expectedStatusCode int,
expectedURL string,
opts ...InitFlowWithOption,
) string {
if hc == nil {
hc = new(http.Client)
Expand All @@ -127,7 +128,7 @@ func SubmitRegistrationForm(
if isAPI {
payload = InitializeRegistrationFlowViaAPI(t, hc, publicTS)
} else {
payload = InitializeRegistrationFlowViaBrowser(t, hc, publicTS, isSPA, false, false)
payload = InitializeRegistrationFlowViaBrowser(t, hc, publicTS, isSPA, false, false, opts...)
}

time.Sleep(time.Millisecond) // add a bit of delay to allow `1ns` to time out.
Expand Down
17 changes: 17 additions & 0 deletions selfservice/flow/flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
package flow

import (
"context"
"github.com/ory/kratos/driver/config"
"net/http"
"net/url"

Expand Down Expand Up @@ -38,3 +40,18 @@ type Flow interface {
AppendTo(*url.URL) *url.URL
GetUI() *container.Container
}

func IsWebViewFlow(ctx context.Context, conf *config.Config, f Flow) (bool, error) {
if f.GetType() != TypeBrowser {
return false, nil
}
requestURL, err := url.Parse(f.GetRequestURL())
if err != nil {
return false, err
}
redirectURL := conf.SelfServiceWebViewRedirectURL(ctx)
if redirectURL == nil {
return false, nil
}
return requestURL.Query().Get("return_to") == redirectURL.String(), nil
}
26 changes: 25 additions & 1 deletion selfservice/flow/login/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"context"
"fmt"
"net/http"
"path"
"time"

"github.com/pkg/errors"
Expand Down Expand Up @@ -237,7 +238,30 @@ func (e *HookExecutor) PostLoginHook(w http.ResponseWriter, r *http.Request, g n
finalReturnTo = rt
}

x.ContentNegotiationRedirection(w, r, s.Declassify(), e.d.Writer(), finalReturnTo)
isWebView, err := flow.IsWebViewFlow(r.Context(), e.d.Config(), a)
if err != nil {
return err
}
if isWebView {
response := &APIFlowResponse{Session: s.Declassify(), Token: s.Token}
required, err := e.requiresAAL2(r, s, a)
if err != nil {
return err
}
if required {
// If AAL is not satisfied, we omit the identity to preserve the user's privacy in case of a phishing attack.
response.Session.Identity = nil
}
w.Header().Set("Content-Type", "application/json")
returnTo.Path = path.Join(returnTo.Path, "success")
query := returnTo.Query()
query.Set("session_token", s.Token)
returnTo.RawQuery = query.Encode()
w.Header().Set("Location", returnTo.String())
e.d.Writer().WriteCode(w, r, http.StatusSeeOther, response)
} else {
x.ContentNegotiationRedirection(w, r, s.Declassify(), e.d.Writer(), finalReturnTo)
}
return nil
}

Expand Down
36 changes: 35 additions & 1 deletion selfservice/flow/registration/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package registration

import (
"net/http"
"path"
"strconv"

"github.com/ory/kratos/ui/node"

Expand Down Expand Up @@ -118,7 +120,39 @@ func (s *ErrorHandler) WriteFlowError(
}

if f.Type == flow.TypeBrowser && !x.IsJSONRequest(r) {
http.Redirect(w, r, f.AppendTo(s.d.Config().SelfServiceFlowRegistrationUI(r.Context())).String(), http.StatusFound)
isWebView, innerErr := flow.IsWebViewFlow(r.Context(), s.d.Config(), f)
if innerErr != nil {
s.forward(w, r, f, innerErr)
return
}

var redirectLocation = ""
if isWebView {
c := s.d.Config()
returnTo, innerErr := x.SecureRedirectTo(r, c.SelfServiceBrowserDefaultReturnTo(r.Context()),
x.SecureRedirectUseSourceURL(f.RequestURL),
x.SecureRedirectAllowURLs(c.SelfServiceBrowserAllowedReturnToDomains(r.Context())),
x.SecureRedirectAllowSelfServiceURLs(c.SelfPublicURL(r.Context())),
x.SecureRedirectOverrideDefaultReturnTo(s.d.Config().SelfServiceFlowLoginReturnTo(r.Context(), f.Active.String())),
)
if innerErr != nil {
s.forward(w, r, f, innerErr)
return
}

if len(f.UI.Messages) > 0 {
query := returnTo.Query()
query.Set("code", strconv.Itoa(int(f.UI.Messages[0].ID)))
query.Set("message", f.UI.Messages[0].Text)
returnTo.RawQuery = query.Encode()
}
returnTo.Path = path.Join(returnTo.Path, "error")
redirectLocation = returnTo.String()

} else {
redirectLocation = f.AppendTo(s.d.Config().SelfServiceFlowRegistrationUI(r.Context())).String()
}
http.Redirect(w, r, redirectLocation, http.StatusFound)
return
}

Expand Down
26 changes: 26 additions & 0 deletions selfservice/hook/session_issuer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
package hook

import (
"github.com/ory/kratos/driver/config"
"net/http"
"net/url"
"path"
"time"

"github.com/pkg/errors"
Expand All @@ -21,6 +24,7 @@ var (

type (
sessionIssuerDependencies interface {
config.Provider
session.ManagementProvider
session.PersistenceProvider
x.WriterProvider
Expand Down Expand Up @@ -52,6 +56,28 @@ func (e *SessionIssuer) ExecutePostRegistrationPostPersistHook(w http.ResponseWr
return errors.WithStack(registration.ErrHookAbortFlow)
}

isWebView, err := flow.IsWebViewFlow(r.Context(), e.r.Config(), a)
if err != nil {
return err
}
if isWebView {
response := &registration.APIFlowResponse{Session: s.Declassify(), Token: s.Token}

w.Header().Set("Content-Type", "application/json")
returnTo, err := url.Parse(a.ReturnTo)
if err != nil {
return err
}
returnTo.Path = path.Join(returnTo.Path, "success")
query := returnTo.Query()
query.Set("session_token", s.Token)
returnTo.RawQuery = query.Encode()
w.Header().Set("Location", returnTo.String())
e.r.Writer().WriteCode(w, r, http.StatusSeeOther, response)

return errors.WithStack(registration.ErrHookAbortFlow)
}

// cookie is issued both for browser and for SPA flows
if err := e.r.SessionManager().IssueCookie(r.Context(), w, r, s); err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion selfservice/strategy/oidc/.schema/link.schema.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$id": "https://schemas.ory.sh/kratos/selfservice/strategy/password/login.schema.json",
"$id": "https://schemas.ory.sh/kratos/selfservice/strategy/oidc/.schema/link.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
Expand Down
25 changes: 25 additions & 0 deletions selfservice/strategy/oidc/.schema/login.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$id": "https://schemas.ory.sh/kratos/selfservice/strategy/oidc/.schema/login.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": [
"provider",
"method"
],
"properties": {
"csrf_token": {
"type": "string"
},
"provider": {
"type": "string",
"minLength": 1
},
"id_token": {
"type": "string",
"minLength": 1
},
"method": {
"type": "string"
}
}
}
4 changes: 4 additions & 0 deletions selfservice/strategy/oidc/.schema/settings.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
},
"traits": {
"description": "This field will be overwritten in registration.go's decoder() method. Do not add anything to this field as it has no effect."
},
"id_token": {
"type": "string",
"minLength": 1
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,28 @@
}
}
}
},
{
"type": "input",
"group": "oidc",
"attributes": {
"name": "provider",
"type": "submit",
"value": "check_audience",
"disabled": false,
"node_type": "input"
},
"messages": [],
"meta": {
"label": {
"id": 1010002,
"text": "Sign in with check_audience",
"type": "info",
"context": {
"provider": "check_audience"
}
}
}
}
]
}
Loading

0 comments on commit 3f5b41c

Please sign in to comment.