Skip to content

Commit

Permalink
oauth2: Improves the consent flow design
Browse files Browse the repository at this point in the history
This patch makes significant changes to the consent flow. First,
the consent flow is being renamed to "User Login and Consent Flow"
and is split into two redirection flows, the "User Login Redirection Flow"
and the "User Consent Flow".

Conceptually, not a lot has changed but the APIs have been cleaned up
and the new flow is a huge step towards OpenID Connect Certification.

Besides easier implementation on the (previously known as) consent app,
this patch introduces a new set of features which lets ORY Hydra
detect previous logins and previously accepted consent requests. In turn,
the user does not need to login or consent on every OAuth2 Authorize Code
Flow.

This patch additionally lays the foundation for revoking tokens per
user or per user and client.

Awesome.

Closes #771
Closes #772
  • Loading branch information
arekkas committed May 4, 2018
1 parent bf226dc commit c19df71
Show file tree
Hide file tree
Showing 154 changed files with 7,709 additions and 5,219 deletions.
30 changes: 25 additions & 5 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,15 @@

[[constraint]]
name = "github.com/ory/fosite"
version = "0.18.0"
version = "0.18.1"

[[constraint]]
name = "github.com/ory/graceful"
version = "0.1.0"

[[constraint]]
name = "github.com/ory/herodot"
version = "0.1.3"
version = "0.2.2"

[[constraint]]
name = "github.com/ory/ladon"
Expand Down
59 changes: 54 additions & 5 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,60 @@ This section summarizes important changes introduced in 1.0.0.

### Major breaking changes

#### Changes to the CLI

The CLI has changed in order to improve developer experience and adopt to the changes made with this release.

##### `hydra host`

The command `hydra host` has been renamed to `hydra serve` as projects ORY Oathkeeper and ORY Keto use the `serve` terminology
as well.

Because this patch removes the internal access control, no root client and root policy will be created upon start up. Thus,
environment variable `FORCE_ROOT_CLIENT_CREDENTIALS` has been removed without replacement.

To better reflect what environment variables touch which system, ISSUER has been renamed to `OAUTH2_ISSUER_URL` and
`CONSENT_URL` has been renamed to `OAUTH2_CONSENT_URL`.

Additionally, flag `--dangerous-force-auto-logon` has been removed it has no effect any more.

##### Access Control & `hydra connect`

WHAT HAPPENED TO THIS COMMAND? TBD

As access control has been removed, most commands (except `token user`, `token client`, `token revoke`, `token introspect`)
work without supplying any credentials at all. The listed exceptions support setting an OAuth 2.0 Client ID and Client Secret
using flags `--client-id` and `--client-secret` or environment variables `OAUTH2_CLIENT_ID` and `OAUTH2_CLIENT_SECRET`.

All other commands, such as `hydra clients create`, still support scenarios where you would need an OAuth2 Access Token.
In those cases, you can supply the access token using flag `--access-token` or environment variable `OAUTH2_ACCESS_TOKEN`.

#### `hydra token validate`

This command has been renamed to `hydra token introspect` to properly reflect that you are performing OAuth 2.0
Token Introspection.

#### `hydra clients create`

As OAuth 2.0 specifies that terminology `scope` does not have a plural `scopes`, we updated the places where the
incorrect `scopes` was used in order to provide a more consistent developer experience.

This command renamed flag `--allowed-scopes` to `--scope`.

#### `hydra migrate ladon`

This command is a relict of an old version of ORY Hydra which is, according to our metrics, not being used any more.

### sdk

AcceptConsentRequest(challenge string, body swagger.AcceptConsentRequest) (*swagger.CompletedRequest, *swagger.APIResponse, error)
AcceptLoginRequest(challenge string, body swagger.AcceptLoginRequest) (*swagger.CompletedRequest, *swagger.APIResponse, error)
RejectConsentRequest(challenge string, body swagger.RejectRequest) (*swagger.CompletedRequest, *swagger.APIResponse, error)
RejectLoginRequest(challenge string, body swagger.RejectRequest) (*swagger.CompletedRequest, *swagger.APIResponse, error)
GetLoginRequest(challenge string) (*swagger.LoginRequest, *swagger.APIResponse, error)
GetConsentRequest(challenge string) (*swagger.ConsentRequest, *swagger.APIResponse, error)


#### Access Control Policies and Warden moved to ORY Keto

#### camelCase JSON is now under_score
Expand Down Expand Up @@ -90,11 +144,6 @@ Minor breaking changes do not require any special upgrade paths, unless you expl
Previously, we disabled the introspection of refresh tokens. This has now changed to comply with the OAuth 2.0 specification.
To distinguish tokens, use the `token_type` in the introspection response. It can either be `access_token` or `refresh_token`.

#### FORCE_ROOT_CLIENT_CREDENTIALS

The variable `FORCE_ROOT_CLIENT_CREDENTIALS` has caused some pain due to url-encoding conventions. It has been replaced
by `FORCE_ROOT_CLIENT_ID` and `FORCE_ROOT_CLIENT_SECRET` which do not need to be encoded.

#### jwk: Forces JWK to have a unique ID

Previously, JSON Web Keys did not have to specify a unique id. JWKs
Expand Down
10 changes: 5 additions & 5 deletions client/manager_sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (

"github.com/jmoiron/sqlx"
"github.com/ory/fosite"
"github.com/ory/hydra/pkg"
"github.com/ory/go-convenience/stringsx"
"github.com/ory/sqlcon"
"github.com/pborman/uuid"
"github.com/pkg/errors"
Expand Down Expand Up @@ -125,16 +125,16 @@ func (d *sqlData) ToClient() *Client {
ID: d.ID,
Name: d.Name,
Secret: d.Secret,
RedirectURIs: pkg.SplitNonEmpty(d.RedirectURIs, "|"),
GrantTypes: pkg.SplitNonEmpty(d.GrantTypes, "|"),
ResponseTypes: pkg.SplitNonEmpty(d.ResponseTypes, "|"),
RedirectURIs: stringsx.Splitx(d.RedirectURIs, "|"),
GrantTypes: stringsx.Splitx(d.GrantTypes, "|"),
ResponseTypes: stringsx.Splitx(d.ResponseTypes, "|"),
Scope: d.Scope,
Owner: d.Owner,
PolicyURI: d.PolicyURI,
TermsOfServiceURI: d.TermsOfServiceURI,
ClientURI: d.ClientURI,
LogoURI: d.LogoURI,
Contacts: pkg.SplitNonEmpty(d.Contacts, "|"),
Contacts: stringsx.Splitx(d.Contacts, "|"),
Public: d.Public,
}
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/clients_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func init() {
clientsCreateCmd.Flags().StringSliceP("callbacks", "c", []string{}, "REQUIRED list of allowed callback URLs")
clientsCreateCmd.Flags().StringSliceP("grant-types", "g", []string{"authorization_code"}, "A list of allowed grant types")
clientsCreateCmd.Flags().StringSliceP("response-types", "r", []string{"code"}, "A list of allowed response types")
clientsCreateCmd.Flags().StringSliceP("allowed-scopes", "a", []string{""}, "A list of allowed scopes")
clientsCreateCmd.Flags().StringSliceP("scope", "a", []string{""}, "The scope the client is allowed to request")
clientsCreateCmd.Flags().Bool("is-public", false, "Use this flag to create a public client")
clientsCreateCmd.Flags().String("secret", "", "Provide the client's secret")
clientsCreateCmd.Flags().StringP("name", "n", "", "The client's name")
Expand Down
20 changes: 10 additions & 10 deletions cmd/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,6 @@ CORE CONTROLS
a separate secret in production.
Example: COOKIE_SECRET=fjah8uFhgjSiuf-AS
- FORCE_ROOT_CLIENT_CREDENTIALS: On first start up, Hydra generates a root client with random id and secret. Use
this environment variable in the form of "FORCE_ROOT_CLIENT_CREDENTIALS=id:secret" to set
the client id and secret yourself. Please www-url-encode the id
and the secret: "FORCE_ROOT_CLIENT_CREDENTIALS=urlencode(id):urlencode(secret)".
Example: FORCE_ROOT_CLIENT_CREDENTIALS=admin:h6hy92tK4dQcZ2EaFsGNRtqg
- PORT: The port hydra should listen on.
Defaults to PORT=4444
Expand Down Expand Up @@ -99,12 +93,18 @@ CORE CONTROLS
OAUTH2 CONTROLS
===============
- CONSENT_URL: The uri of the consent endpoint.
Example: CONSENT_URL=https://id.myapp.com/consent
- OAUTH2_ERROR_URL: A dedicated endpoint that shows critical errors in a user-friendly way.
Example: OAUTH2_ERROR_URL=https://id.myapp.com/error
- OAUTH2_CONSENT_URL: The consent provider's URL.
Example: OAUTH2_CONSENT_URL=https://id.myapp.com/consent
- OAUTH2_LOGIN_URL: The login provider's URL.
Example: OAUTH2_LOGIN_URL=https://id.myapp.com/login
- ISSUER: Issuer is the public URL of your Hydra installation. It is used for OAuth2 and OpenID Connect and must be
- OAUTH2_ISSUER_URL: Issuer is the public URL of your Hydra installation. It is used for OAuth2 and OpenID Connect and must be
specified and using HTTPS protocol, unless --dangerous-force-http is set.
Example: ISSUER=https://hydra.myapp.com/
Example: OAUTH2_ISSUER_URL=https://hydra.myapp.com/
- AUTH_CODE_LIFESPAN: Lifespan of OAuth2 authorize codes. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
Defaults to AUTH_CODE_LIFESPAN=10m
Expand Down
16 changes: 11 additions & 5 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,14 @@ func initConfig() {
viper.BindEnv("CLIENT_ID")
viper.SetDefault("CLIENT_ID", "")

viper.BindEnv("CONSENT_URL")
viper.SetDefault("CONSENT_URL", oauth2.DefaultConsentPath)
viper.BindEnv("OAUTH2_CONSENT_URL")
viper.SetDefault("OAUTH2_CONSENT_URL", oauth2.DefaultConsentPath)

viper.BindEnv("OAUTH2_LOGIN_URL")
viper.SetDefault("OAUTH2_LOGIN_URL", oauth2.DefaultConsentPath)

viper.BindEnv("OAUTH2_ERROR_URL")
viper.SetDefault("OAUTH2_ERROR_URL", oauth2.DefaultErrorPath)

viper.BindEnv("DATABASE_PLUGIN")
viper.SetDefault("DATABASE_PLUGIN", "")
Expand All @@ -131,8 +137,8 @@ func initConfig() {
viper.BindEnv("PORT")
viper.SetDefault("PORT", 4444)

viper.BindEnv("ISSUER")
viper.SetDefault("ISSUER", "http://localhost:4444")
viper.BindEnv("OAUTH2_ISSUER_URL")
viper.SetDefault("OAUTH2_ISSUER_URL", "http://localhost:4444")

viper.BindEnv("BCRYPT_COST")
viper.SetDefault("BCRYPT_COST", 10)
Expand Down Expand Up @@ -176,7 +182,7 @@ func initConfig() {
fmt.Println("")
}

iss := viper.Get("ISSUER")
iss := viper.Get("OAUTH2_ISSUER_URL")
viper.Set("ISSUER", strings.TrimSuffix(iss.(string), "/"))

if err := viper.Unmarshal(c); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func TestExecute(t *testing.T) {
os.Setenv("DATABASE_URL", "memory")
os.Setenv("FORCE_ROOT_CLIENT_ID", "admin")
os.Setenv("FORCE_ROOT_CLIENT_SECRET", "pw")
os.Setenv("ISSUER", "https://localhost:4444/")
os.Setenv("OAUTH2_ISSUER_URL", "https://localhost:4444/")
copy(osArgs, os.Args)

for _, c := range []struct {
Expand Down
15 changes: 5 additions & 10 deletions cmd/server/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
"github.com/ory/herodot"
"github.com/ory/hydra/client"
"github.com/ory/hydra/config"
"github.com/ory/hydra/consent"
"github.com/ory/hydra/jwk"
"github.com/ory/hydra/oauth2"
"github.com/ory/hydra/pkg"
Expand All @@ -47,6 +48,8 @@ import (
"github.com/urfave/negroni"
)

var _ = &consent.Handler{}

func parseCorsOptions() cors.Options {
allowCredentials, _ := strconv.ParseBool(viper.GetString("CORS_ALLOWED_CREDENTIALS"))
debug, _ := strconv.ParseBool(viper.GetString("CORS_DEBUG"))
Expand Down Expand Up @@ -120,12 +123,6 @@ func RunHost(c *config.Config) func(cmd *cobra.Command, args []string) {
},
})

if ok, _ := cmd.Flags().GetBool("dangerous-auto-logon"); ok {
logger.Warnln("Do not use flag --dangerous-auto-logon in production.")
err := c.Persist()
pkg.Must(err, "Could not write configuration file: %s", err)
}

err := graceful.Graceful(func() error {
var err error
logger.Infof("Setting up http server on %s", c.GetAddress())
Expand All @@ -149,7 +146,7 @@ type Handler struct {
Clients *client.Handler
Keys *jwk.Handler
OAuth2 *oauth2.Handler
Consent *oauth2.ConsentSessionHandler
Consent *consent.Handler
Config *config.Config
H herodot.Writer
}
Expand All @@ -168,11 +165,9 @@ func (h *Handler) registerRoutes(router *httprouter.Router) {
// Set up handlers
h.Clients = newClientHandler(c, router, clientsManager)
h.Keys = newJWKHandler(c, router)
h.Consent = newConsentHanlder(c, router)
h.Consent = newConsentHandler(c, router)
h.OAuth2 = newOAuth2Handler(c, router, ctx.ConsentManager, oauth2Provider, idTokenKeyID)
_ = newHealthHandler(c, router)

h.createRootIfNewInstall(c)
}

func (h *Handler) rejectInsecureRequests(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
Expand Down
20 changes: 10 additions & 10 deletions cmd/server/handler_consent_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,23 @@ import (
"github.com/julienschmidt/httprouter"
"github.com/ory/herodot"
"github.com/ory/hydra/config"
"github.com/ory/hydra/oauth2"
"github.com/ory/hydra/consent"
)

func injectConsentManager(c *config.Config) {
var ctx = c.Context()
var manager oauth2.ConsentRequestManager
var manager consent.Manager

switch con := ctx.Connection.(type) {
case *config.MemoryConnection:
manager = oauth2.NewConsentRequestMemoryManager()
manager = consent.NewMemoryManager()
break
case *config.SQLConnection:
manager = oauth2.NewConsentRequestSQLManager(con.GetDatabase())
panic("not implemented yet")
break
case *config.PluginConnection:
var err error
if manager, err = con.NewConsentRequestManager(); err != nil {
if manager, err = con.NewConsentManager(); err != nil {
c.GetLogger().Fatalf("Could not load client manager plugin %s", err)
}
break
Expand All @@ -49,13 +49,13 @@ func injectConsentManager(c *config.Config) {
}

ctx.ConsentManager = manager

}

func newConsentHanlder(c *config.Config, router *httprouter.Router) *oauth2.ConsentSessionHandler {
h := &oauth2.ConsentSessionHandler{
H: herodot.NewJSONWriter(c.GetLogger()),
ResourcePrefix: c.AccessControlResourcePrefix,
func newConsentHandler(c *config.Config, router *httprouter.Router) *consent.Handler {
var ctx = c.Context()
h := &consent.Handler{
H: herodot.NewJSONWriter(c.GetLogger()),
M: ctx.ConsentManager,
}

h.SetRoutes(router)
Expand Down
Loading

0 comments on commit c19df71

Please sign in to comment.