Repository: ory/kratos
Pull request #736 (2020-10-05 08:45:04)

feat: prepare v0.5.0 release
## Related issue

<!--
Please link the GitHub issue this pull request resolves in the format of `#1234`. If you discussed this change
with a maintainer, please mention her/him using the `@` syntax (e.g. `@aeneasr`).

If this change neither resolves an existing issue nor has sign-off from one of the maintainers, there is a
chance substantial changes will be requested or that the changes will be rejected.

You can discuss changes with maintainers either in the [ORY Community Forums](https://community.ory.sh/) or
join the [ORY Chat](https://www.ory.sh/chat).
-->

## Proposed changes

<!--
Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request.
-->

## Checklist

<!--
Put an `x` in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of
them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code.
-->

- [ ] I have read the [contributing guidelines](../blob/master/CONTRIBUTING.md).
- [ ] I have read the [security policy](../security/policy).
- [ ] I confirm that this pull request does not address a security
      vulnerability. If this pull request addresses a security. vulnerability, I
      confirm that I got green light (please contact
      [security@ory.sh](mailto:security@ory.sh)) from the maintainers to push
      the changes.
- [ ] I have added tests that prove my fix is effective or that my feature
      works.
- [ ] I have added or changed [the documentation](docs/docs).

## Further comments

<!--
If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution
you did and what alternatives you considered, etc...
-->


nodkz: @aeneasr v0.5 has a lot of improvements and fixes. Should we wait for v0.5 eg week or two, or better try to spin up ORY under currently available versions?

Have you any ETA for v0.5.0-alpha.1?

PS. ORY is an amazing project, hope it became an enterprise-grade auth tool like keycloak in the nearest future. And, of course, even better than the current monolithic solutions on Java.
aeneasr: We don’t give out timelines but 1-2 weeks to go I guess!

> On 11. Oct 2020, at 14:51, Pablo Damnhorns <notifications@github.com> wrote:
> 
> ﻿
> @aeneasr v0.5 has a lot of improvements and fixes. Should we wait for v0.5 eg week or two, or better try to spin up ORY under currently available versions?
> 
> Have you any ETA for v0.5.0-alpha.1?
> 
> PS. ORY is an amazing project, hope it became an enterprise-grade auth tool like keycloak in the nearest future. And, of course, even better than the current monolithic solutions on Java.
> 
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub, or unsubscribe.


Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index f0144bfb6..2b31270f2 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,13 +1,13 @@
 version: 2.1
 
 orbs:
-  changelog: ory/changelog@0.1.3
-  goreleaser: ory/goreleaser@0.1.17
+  changelog: ory/changelog@0.1.4
+  goreleaser: ory/goreleaser@0.1.20
   slack: circleci/slack@3.4.2
-  sdk: ory/sdk@0.1.37
-  nancy: ory/nancy@0.0.12
+  sdk: ory/sdk@0.1.38
+  nancy: ory/nancy@0.0.13
   docs: ory/docs@0.0.8
-  golangci: ory/golangci@0.0.8
+  golangci: ory/golangci@0.0.9
 
 jobs:
   test:
diff --git a/.schema/api.swagger.json b/.schema/api.swagger.json
index 8e78ef468..bed71996c 100755
--- a/.schema/api.swagger.json
+++ b/.schema/api.swagger.json
@@ -702,30 +702,19 @@
         "summary": "Complete Login Flow with Username/Email Password Method",
         "operationId": "completeSelfServiceLoginFlowWithPasswordMethod",
         "parameters": [
-          {
-            "type": "string",
-            "description": "The user's password.",
-            "name": "password",
-            "in": "query"
-          },
-          {
-            "type": "string",
-            "description": "Identifier is the email or username of the user trying to log in.",
-            "name": "identifier",
-            "in": "query"
-          },
-          {
-            "type": "string",
-            "description": "Sending the anti-csrf token is only required for browser login flows.",
-            "name": "csrf_token",
-            "in": "query"
-          },
           {
             "type": "string",
             "description": "The Flow ID",
             "name": "flow",
             "in": "query",
             "required": true
+          },
+          {
+            "name": "Body",
+            "in": "body",
+            "schema": {
+              "$ref": "#/definitions/CompleteSelfServiceLoginFlowWithPasswordMethod"
+            }
           }
         ],
         "responses": {
@@ -1072,8 +1061,7 @@
             "name": "Payload",
             "in": "body",
             "schema": {
-              "type": "object",
-              "additionalProperties": true
+              "type": "object"
             }
           }
         ],
@@ -1335,6 +1323,21 @@
         ],
         "summary": "Complete Settings Flow with Profile Method",
         "operationId": "completeSelfServiceSettingsFlowWithProfileMethod",
+        "parameters": [
+          {
+            "type": "string",
+            "description": "Flow is flow ID.",
+            "name": "flow",
+            "in": "query"
+          },
+          {
+            "name": "Payload",
+            "in": "body",
+            "schema": {
+              "type": "object"
+            }
+          }
+        ],
         "responses": {
           "200": {
             "description": "settingsFlow",
@@ -1630,7 +1633,7 @@
               "$ref": "#/definitions/session"
             }
           },
-          "403": {
+          "401": {
             "description": "genericError",
             "schema": {
               "$ref": "#/definitions/genericError"
@@ -1668,6 +1671,23 @@
     }
   },
   "definitions": {
+    "CompleteSelfServiceLoginFlowWithPasswordMethod": {
+      "type": "object",
+      "properties": {
+        "csrf_token": {
+          "description": "Sending the anti-csrf token is only required for browser login flows.",
+          "type": "string"
+        },
+        "identifier": {
+          "description": "Identifier is the email or username of the user trying to log in.",
+          "type": "string"
+        },
+        "password": {
+          "description": "The user's password.",
+          "type": "string"
+        }
+      }
+    },
     "CompleteSelfServiceSettingsFlowWithPasswordMethod": {
       "type": "object",
       "required": [
@@ -1722,30 +1742,6 @@
       "type": "string",
       "title": "CredentialsType  represents several different credential types, like password credentials, passwordless credentials,"
     },
-    "FlowMethodConfig": {
-      "type": "object",
-      "required": [
-        "action",
-        "method",
-        "fields"
-      ],
-      "properties": {
-        "action": {
-          "description": "Action should be used as the form action URL `\u003cform action=\"{{ .Action }}\" method=\"post\"\u003e`.",
-          "type": "string"
-        },
-        "fields": {
-          "$ref": "#/definitions/formFields"
-        },
-        "messages": {
-          "$ref": "#/definitions/Messages"
-        },
-        "method": {
-          "description": "Method is the form method (e.g. POST)",
-          "type": "string"
-        }
-      }
-    },
     "ID": {
       "type": "integer",
       "format": "int64"
@@ -2500,7 +2496,7 @@
       ],
       "properties": {
         "config": {
-          "$ref": "#/definitions/FlowMethodConfig"
+          "$ref": "#/definitions/settingsFlowMethodConfig"
         },
         "method": {
           "description": "Method is the name of this flow method.",
@@ -2508,6 +2504,30 @@
         }
       }
     },
+    "settingsFlowMethodConfig": {
+      "type": "object",
+      "required": [
+        "action",
+        "method",
+        "fields"
+      ],
+      "properties": {
+        "action": {
+          "description": "Action should be used as the form action URL `\u003cform action=\"{{ .Action }}\" method=\"post\"\u003e`.",
+          "type": "string"
+        },
+        "fields": {
+          "$ref": "#/definitions/formFields"
+        },
+        "messages": {
+          "$ref": "#/definitions/Messages"
+        },
+        "method": {
+          "description": "Method is the form method (e.g. POST)",
+          "type": "string"
+        }
+      }
+    },
     "settingsViaApiResponse": {
       "description": "The Response for Settings Flows via API",
       "type": "object",
@@ -2625,7 +2645,7 @@
   "securityDefinitions": {
     "sessionToken": {
       "type": "apiKey",
-      "name": "Authorization",
+      "name": "X-Session-Token",
       "in": "header"
     }
   },
diff --git a/.schema/config.schema.json b/.schema/config.schema.json
index fad927caa..85da3af43 100644
--- a/.schema/config.schema.json
+++ b/.schema/config.schema.json
@@ -694,6 +694,117 @@
         "public": {
           "type": "object",
           "properties": {
+            "cors": {
+              "type": "object",
+              "additionalProperties": false,
+              "description": "Configures Cross Origin Resource Sharing for public endpoints.",
+              "properties": {
+                "enabled": {
+                  "type": "boolean",
+                  "description": "Sets whether CORS is enabled.",
+                  "default": false
+                },
+                "allowed_origins": {
+                  "type": "array",
+                  "description": "A list of origins a cross-domain request can be executed from. If the special * value is present in the list, all origins will be allowed. An origin may contain a wildcard (*) to replace 0 or more characters (i.e.: http://*.domain.com). Only one wildcard can be used per origin.",
+                  "items": {
+                    "type": "string",
+                    "minLength": 1,
+                    "not": {
+                      "type": "string",
+                      "description": "does match all strings that contain two or more (*)",
+                      "pattern": ".*\\*.*\\*.*"
+                    },
+                    "anyOf": [
+                      {
+                        "format": "uri"
+                      },
+                      {
+                        "const": "*"
+                      }
+                    ]
+                  },
+                  "uniqueItems": true,
+                  "default": [
+                    "*"
+                  ],
+                  "examples": [
+                    [
+                      "https://example.com",
+                      "https://*.example.com",
+                      "https://*.foo.example.com"
+                    ]
+                  ]
+                },
+                "allowed_methods": {
+                  "type": "array",
+                  "description": "A list of HTTP methods the user agent is allowed to use with cross-domain requests.",
+                  "default": [
+                    "POST",
+                    "GET",
+                    "PUT",
+                    "PATCH",
+                    "DELETE"
+                  ],
+                  "items": {
+                    "type": "string",
+                    "enum": [
+                      "POST",
+                      "GET",
+                      "PUT",
+                      "PATCH",
+                      "DELETE",
+                      "CONNECT",
+                      "HEAD",
+                      "OPTIONS",
+                      "TRACE"
+                    ]
+                  }
+                },
+                "allowed_headers": {
+                  "type": "array",
+                  "description": "A list of non simple headers the client is allowed to use with cross-domain requests.",
+                  "default": [
+                    "Authorization",
+                    "Content-Type"
+                  ],
+                  "items": {
+                    "type": "string"
+                  }
+                },
+                "exposed_headers": {
+                  "type": "array",
+                  "description": "Sets which headers are safe to expose to the API of a CORS API specification.",
+                  "default": [
+                    "Content-Type"
+                  ],
+                  "items": {
+                    "type": "string"
+                  }
+                },
+                "allow_credentials": {
+                  "type": "boolean",
+                  "description": "Sets whether the request can include user credentials like cookies, HTTP authentication or client side SSL certificates.",
+                  "default": true
+                },
+                "options_passthrough": {
+                  "type": "boolean",
+                  "description": "TODO",
+                  "default": false
+                },
+                "max_age": {
+                  "type": "integer",
+                  "description": "Sets how long (in seconds) the results of a preflight request can be cached. If set to 0, every request is preceded by a preflight request.",
+                  "default": 0,
+                  "minimum": 0
+                },
+                "debug": {
+                  "type": "boolean",
+                  "description": "Adds additional log output to debug server side CORS issues.",
+                  "default": false
+                }
+              }
+            },
             "base_url": {
               "title": "Public Base URL",
               "description": "The URL where the public endpoint is exposed at.",
diff --git a/Makefile b/Makefile
index 90fd8e83a..0c82314f4 100644
--- a/Makefile
+++ b/Makefile
@@ -67,10 +67,8 @@ mocks: .bin/mockgen
 		mockgen -mock_names Manager=MockLoginExecutorDependencies -package internal -destination internal/hook_login_executor_dependencies.go github.com/ory/kratos/selfservice loginExecutorDependencies
 
 .PHONY: install
-install: .bin/packr2 .bin/pack
-		make pack
+install: pack
 		GO111MODULE=on go install -tags sqlite .
-		packr2 clean
 
 .PHONY: test-resetdb
 test-resetdb:
diff --git a/cmd/daemon/serve.go b/cmd/daemon/serve.go
index e65c242a4..30d51cc61 100644
--- a/cmd/daemon/serve.go
+++ b/cmd/daemon/serve.go
@@ -5,6 +5,10 @@ import (
 	"strings"
 	"sync"
 
+	"github.com/rs/cors"
+
+	"github.com/ory/x/corsx"
+
 	"github.com/ory/kratos/metrics/prometheus"
 
 	"github.com/ory/analytics-go/v4"
@@ -63,9 +67,15 @@ func servePublic(d driver.Driver, wg *sync.WaitGroup, cmd *cobra.Command, args [
 		n.Use(tracer)
 	}
 
+	var handler http.Handler = n
+	if corsx.IsEnabled(l, "serve.public") {
+		handler = cors.New(
+			corsx.ParseOptions(l, "serve.public")).Handler(handler)
+	}
+
 	server := graceful.WithDefaults(&http.Server{
 		Addr:    c.PublicListenOn(),
-		Handler: context.ClearHandler(n),
+		Handler: context.ClearHandler(handler),
 	})
 
 	l.Printf("Starting the public httpd on: %s", server.Addr)
diff --git a/cmd/identities/delete.go b/cmd/identities/delete.go
index bf7a91ae1..d4a005740 100644
--- a/cmd/identities/delete.go
+++ b/cmd/identities/delete.go
@@ -16,12 +16,12 @@ var deleteCmd = &cobra.Command{
 	Use:   "delete <id-0 [id-1 ...]>",
 	Short: "Delete identities by ID",
 	Long: fmt.Sprintf(`This command deletes one or more identities by ID. To delete an identity by some selector, e.g. the recovery email address, use the list command in combination with jq.
-Example: delete the identity with the recovery email address "foo@bar.com":
-
-kratos identities delete $(kratos identities list --format json | jq -r 'map(select(.recovery_addresses[].value == "foo@bar.com")) | .[].id')
 
 %s
 `, clihelpers.WarningJQIsComplicated),
+	Example: `To delete the identity with the recovery email address "foo@bar.com", run:
+
+	$ kratos identities delete $(kratos identities list --format json | jq -r 'map(select(.recovery_addresses[].value == "foo@bar.com")) | .[].id')`,
 	Args: cobra.MinimumNArgs(1),
 	RunE: func(cmd *cobra.Command, args []string) error {
 		c := cliclient.NewClient(cmd)
diff --git a/cmd/identities/get.go b/cmd/identities/get.go
index 1dac2d5fb..3ef08c99b 100644
--- a/cmd/identities/get.go
+++ b/cmd/identities/get.go
@@ -17,12 +17,12 @@ var getCmd = &cobra.Command{
 	Use:   "get <id-0 [id-1 ...]>",
 	Short: "Get one or more identities by ID",
 	Long: fmt.Sprintf(`This command gets all the details about an identity. To get an identity by some selector, e.g. the recovery email address, use the list command in combination with jq.
-Example: get the identities with the recovery email address at the domain "ory.sh":
-
-kratos identities get $(kratos identities list --format json | jq -r 'map(select(.recovery_addresses[].value | endswith("@ory.sh"))) | .[].id')
 
 %s
 `, clihelpers.WarningJQIsComplicated),
+	Example: `To get the identities with the recovery email address at the domain "ory.sh", run:
+
+	$ kratos identities get $(kratos identities list --format json | jq -r 'map(select(.recovery_addresses[].value | endswith("@ory.sh"))) | .[].id')`,
 	Args: cobra.MinimumNArgs(1),
 	RunE: func(cmd *cobra.Command, args []string) error {
 		c := cliclient.NewClient(cmd)
diff --git a/cmd/identities/import.go b/cmd/identities/import.go
index f3f95e025..a2e349c83 100644
--- a/cmd/identities/import.go
+++ b/cmd/identities/import.go
@@ -18,11 +18,9 @@ import (
 var importCmd = &cobra.Command{
 	Use:   "import <file.json [file-2.json [file-3.json] ...]>",
 	Short: "Import identities from files or STD_IN",
-	Long: `Import identities from files or STD_IN:
-
-	kratos identities import file.json
-
-	cat file.json | kratos identities import
+	Example: `$ kratos identities import file.json
+$ cat file.json | kratos identities import`,
+	Long: `Import identities from files or STD_IN.
 
 Files can contain only a single or an array of identities. The validity of files can be tested beforehand using "... identities validate".
 
diff --git a/contrib/quickstart/kratos/email-password/kratos.yml b/contrib/quickstart/kratos/email-password/kratos.yml
index e4c4b7df4..6c8dfc6b8 100644
--- a/contrib/quickstart/kratos/email-password/kratos.yml
+++ b/contrib/quickstart/kratos/email-password/kratos.yml
@@ -5,6 +5,8 @@ dsn: memory
 serve:
   public:
     base_url: http://127.0.0.1:4433/
+    cors:
+      enabled: true
   admin:
     base_url: http://kratos:4434/
 
diff --git a/docs/docs/cli/kratos-identities-delete.md b/docs/docs/cli/kratos-identities-delete.md
index 804e77d7d..1d6fdc69e 100644
--- a/docs/docs/cli/kratos-identities-delete.md
+++ b/docs/docs/cli/kratos-identities-delete.md
@@ -17,9 +17,6 @@ Delete identities by ID
 ### Synopsis
 
 This command deletes one or more identities by ID. To delete an identity by some selector, e.g. the recovery email address, use the list command in combination with jq.
-Example: delete the identity with the recovery email address "foo@bar.com":
-
-kratos identities delete \$(kratos identities list --format json | jq -r 'map(select(.recovery_addresses[].value == "foo@bar.com")) | .[].id')
 
 We have to admit, this is not easy if you don't speak jq fluently. What about opening an issue and telling us what predefined selectors you want to have? https://github.com/ory/kratos/issues/new/choose
 
@@ -27,6 +24,14 @@ We have to admit, this is not easy if you don't speak jq fluently. What about op
 kratos identities delete <id-0 [id-1 ...]> [flags]
 ```
 
+### Examples
+
+```
+To delete the identity with the recovery email address "foo@bar.com", run:
+
+	$ kratos identities delete $(kratos identities list --format json | jq -r 'map(select(.recovery_addresses[].value == "foo@bar.com")) | .[].id')
+```
+
 ### Options
 
 ```
diff --git a/docs/docs/cli/kratos-identities-get.md b/docs/docs/cli/kratos-identities-get.md
index 3a4322bdd..2ddceeefc 100644
--- a/docs/docs/cli/kratos-identities-get.md
+++ b/docs/docs/cli/kratos-identities-get.md
@@ -17,9 +17,6 @@ Get one or more identities by ID
 ### Synopsis
 
 This command gets all the details about an identity. To get an identity by some selector, e.g. the recovery email address, use the list command in combination with jq.
-Example: get the identities with the recovery email address at the domain "ory.sh":
-
-kratos identities get \$(kratos identities list --format json | jq -r 'map(select(.recovery_addresses[].value | endswith("@ory.sh"))) | .[].id')
 
 We have to admit, this is not easy if you don't speak jq fluently. What about opening an issue and telling us what predefined selectors you want to have? https://github.com/ory/kratos/issues/new/choose
 
@@ -27,6 +24,14 @@ We have to admit, this is not easy if you don't speak jq fluently. What about op
 kratos identities get <id-0 [id-1 ...]> [flags]
 ```
 
+### Examples
+
+```
+To get the identities with the recovery email address at the domain "ory.sh", run:
+
+	$ kratos identities get $(kratos identities list --format json | jq -r 'map(select(.recovery_addresses[].value | endswith("@ory.sh"))) | .[].id')
+```
+
 ### Options
 
 ```
diff --git a/docs/docs/cli/kratos-identities-import.md b/docs/docs/cli/kratos-identities-import.md
index e0d29b0b5..3eb07b898 100644
--- a/docs/docs/cli/kratos-identities-import.md
+++ b/docs/docs/cli/kratos-identities-import.md
@@ -16,11 +16,7 @@ Import identities from files or STD_IN
 
 ### Synopsis
 
-Import identities from files or STD_IN:
-
-    kratos identities import file.json
-
-    cat file.json | kratos identities import
+Import identities from files or STD_IN.
 
 Files can contain only a single or an array of identities. The validity of files can be tested beforehand using "... identities validate".
 
@@ -30,6 +26,13 @@ WARNING: Importing credentials is not yet supported.
 kratos identities import <file.json [file-2.json [file-3.json] ...]> [flags]
 ```
 
+### Examples
+
+```
+$ kratos identities import file.json
+$ cat file.json | kratos identities import
+```
+
 ### Options
 
 ```
diff --git a/docs/docs/contributing.md b/docs/docs/contributing.md
index 925fc2fc7..05704e529 100644
--- a/docs/docs/contributing.md
+++ b/docs/docs/contributing.md
@@ -86,7 +86,8 @@ Please follow these guidelines when formatting source code:
 Please provide documentation when changing, removing, or adding features.
 Documentation resides in the project's `docs` folder.
 
-In cases where a project does not have a `docs` folder check the README for instructions.
+In cases where a project does not have a `docs` folder check the README for
+instructions.
 
 The commands listed below work exclusively for projects with a `docs` folder
 
@@ -108,8 +109,8 @@ To start a local development server with hot reloading, run:
 $ npm start
 ```
 
-This command opens up a browser window. Please note that changes to the sidebar are not hot-reloaded
-and require a restart of the command.
+This command opens up a browser window. Please note that changes to the sidebar
+are not hot-reloaded and require a restart of the command.
 
 #### Build
 
@@ -146,27 +147,32 @@ please include a note in your commit message explaining why.
 
 ## How We Organize Our Work
 
-All repositories in the [ORY organization](https://github.com/ory) have their issues and pull requests
-monitored in the [ORY Monitoring Board](https://github.com/orgs/ory/projects/9). This allows
-for a transparent backlog of unanswered issues and pull requests across the ecosystem from those
-who are allowed to merge pull requests to the main branch.
+All repositories in the [ORY organization](https://github.com/ory) have their
+issues and pull requests monitored in the
+[ORY Monitoring Board](https://github.com/orgs/ory/projects/9). This allows for
+a transparent backlog of unanswered issues and pull requests across the
+ecosystem from those who are allowed to merge pull requests to the main branch.
 
 The process is as follows:
 
-1. _Cards_ represent open issues and pull requests and are automatically assigned to the **Triage** column if
-   the author is not one of the maintainers and no maintainer has answered yet.
-2. A maintainer assigns the issue or pull request to someone or adds the label _help wanted_
-   which moves the card to **Requires Action**.
-3. If a maintainer leaves a comment or review, the card moves to **Pending Reply**, implying that
-   the original author needs to do something (e.g. implement a change, explain something in more detail, ...).
-4. If a non-maintainer pushes changes to the pull request or leaves a comment, the card moves
-   back to **Requires Action**.
-5. If a card stays inactive for 60 days or more days, we assume that public interest in the issue
-   or change has waned, **archiving** the card.
-6. If the issue is closed or the pull request merged or closed, the card is **archived** as well.
-
-We try our best to answer all issues and review all pull requests and hope that this transparent way
-of keeping a backlog helps you better understand how heavy the workload is.
+1. _Cards_ represent open issues and pull requests and are automatically
+   assigned to the **Triage** column if the author is not one of the maintainers
+   and no maintainer has answered yet.
+2. A maintainer assigns the issue or pull request to someone or adds the label
+   _help wanted_ which moves the card to **Requires Action**.
+3. If a maintainer leaves a comment or review, the card moves to **Pending
+   Reply**, implying that the original author needs to do something (e.g.
+   implement a change, explain something in more detail, ...).
+4. If a non-maintainer pushes changes to the pull request or leaves a comment,
+   the card moves back to **Requires Action**.
+5. If a card stays inactive for 60 days or more days, we assume that public
+   interest in the issue or change has waned, **archiving** the card.
+6. If the issue is closed or the pull request merged or closed, the card is
+   **archived** as well.
+
+We try our best to answer all issues and review all pull requests and hope that
+this transparent way of keeping a backlog helps you better understand how heavy
+the workload is.
 
 ## Communication
 
diff --git a/docs/docs/guides/login-session.mdx b/docs/docs/guides/login-session.mdx
index 8f3421df8..337a7b33e 100644
--- a/docs/docs/guides/login-session.mdx
+++ b/docs/docs/guides/login-session.mdx
@@ -145,6 +145,8 @@ Token as a bearer token in the HTTP Authorization Header:
 $ sessionToken=oFZzgLpsacUpUy2cvQPtrGa2046WcXCR
 $ curl -s -X POST -H  "Accept: application/json" \
     -H "Authorization: Bearer $sessionToken" \
+    # OR: \
+    # -H "X-Session-Token: $sessionToken" \
     http://127.0.0.1:4433/sessions/whoami | jq
 
 {
diff --git a/docs/docs/guides/setting-up-cors.mdx b/docs/docs/guides/setting-up-cors.mdx
new file mode 100644
index 000000000..10afa6a70
--- /dev/null
+++ b/docs/docs/guides/setting-up-cors.mdx
@@ -0,0 +1,31 @@
+---
+id: setting-up-cors
+title: Setting up Cross-origin resource sharing (CORS)
+---
+
+ORY Kratos' Public API supports CORS out of the box. A reference for this configuration
+can be found in the [Configuration Reference](../reference/configuration.md).
+
+For CORS to work properly, we encourage to set the following values:
+
+```yaml
+serve:
+  public:
+    cors:
+      enabled: true
+      allowed_origins:
+        - https://example.org
+        - https://*.example.org
+      allowed_methods:
+        - POST
+        - GET
+        - PUT
+        - PATCH
+        - DELETE
+      allowed_headers:
+        - Authorization
+        - Cookie
+      exposed_headers:
+        - Content-Type
+        - Set-Cookie
+```
diff --git a/docs/docs/quickstart.mdx b/docs/docs/quickstart.mdx
index 7a3be5db4..dc4a1d44e 100644
--- a/docs/docs/quickstart.mdx
+++ b/docs/docs/quickstart.mdx
@@ -49,7 +49,8 @@ needs users. Therefore, we need:
 - Profile management ("update first name", "update avatar", etc.)
 - Credentials Management ("add a new recovery email", "change password", etc.)
 - Account Recovery ("password reset")
-- Two Factor Authentication with Google Authenticator
+- Email Verification
+- Two Factor Authentication
 - "Sign in with Google" and "Sign in with GitHub"
 
 and of course:
diff --git a/docs/sidebar.json b/docs/sidebar.json
index 9ddfb8c78..b4068fe30 100644
--- a/docs/sidebar.json
+++ b/docs/sidebar.json
@@ -35,12 +35,13 @@
   "Administration": ["admin/managing-users-identities"],
   "Guides": [
     "guides/sign-in-with-github-google-facebook-linkedin", 
-    "guides/zero-trust-iap-proxy-identity-access-proxy", 
+    "guides/login-session", 
+    "guides/configuring-cookies", 
+    "guides/setting-up-cors", 
     "guides/account-recovery-password-reset", 
     "guides/account-activation-email-verification", 
+    "guides/zero-trust-iap-proxy-identity-access-proxy", 
     "guides/multi-tenancy-multitenant", 
-    "guides/configuring-cookies", 
-    "guides/login-session", 
     "guides/secret-key-rotation", 
     "guides/high-availability-ha", 
     "guides/docker"
@@ -68,96 +69,6 @@
         "cli/kratos-remote-version"
       ]
     }, 
-    {
-      "items": [
-        "cli/kratos", 
-        "cli/kratos-identities", 
-        "cli/kratos-identities-delete", 
-        "cli/kratos-identities-get", 
-        "cli/kratos-identities-list", 
-        "cli/kratos-identities-patch", 
-        "cli/kratos-identities-put", 
-        "cli/kratos-identities-validate", 
-        "cli/kratos-jsonnet", 
-        "cli/kratos-jsonnet-format", 
-        "cli/kratos-jsonnet-lint", 
-        "cli/kratos-remote", 
-        "cli/kratos-remote-status", 
-        "cli/kratos-remote-version"
-      ]
-    }, 
-    {
-      "items": [
-        "cli/kratos", 
-        "cli/kratos-identities", 
-        "cli/kratos-identities-delete", 
-        "cli/kratos-identities-get", 
-        "cli/kratos-identities-import", 
-        "cli/kratos-identities-list", 
-        "cli/kratos-identities-patch", 
-        "cli/kratos-identities-validate", 
-        "cli/kratos-jsonnet", 
-        "cli/kratos-jsonnet-format", 
-        "cli/kratos-jsonnet-lint", 
-        "cli/kratos-remote", 
-        "cli/kratos-remote-status", 
-        "cli/kratos-remote-version"
-      ]
-    }, 
-    {
-      "items": [
-        "cli/kratos", 
-        "cli/kratos-identities", 
-        "cli/kratos-identities-delete", 
-        "cli/kratos-identities-get", 
-        "cli/kratos-identities-import", 
-        "cli/kratos-identities-list", 
-        "cli/kratos-identities-patch", 
-        "cli/kratos-identities-validate", 
-        "cli/kratos-jsonnet", 
-        "cli/kratos-jsonnet-format", 
-        "cli/kratos-jsonnet-lint", 
-        "cli/kratos-remote", 
-        "cli/kratos-remote-status", 
-        "cli/kratos-remote-version"
-      ]
-    }, 
-    {
-      "items": [
-        "cli/kratos", 
-        "cli/kratos-identities", 
-        "cli/kratos-identities-delete", 
-        "cli/kratos-identities-get", 
-        "cli/kratos-identities-import", 
-        "cli/kratos-identities-list", 
-        "cli/kratos-identities-patch", 
-        "cli/kratos-identities-validate", 
-        "cli/kratos-jsonnet", 
-        "cli/kratos-jsonnet-format", 
-        "cli/kratos-jsonnet-lint", 
-        "cli/kratos-remote", 
-        "cli/kratos-remote-status", 
-        "cli/kratos-remote-version"
-      ]
-    }, 
-    {
-      "items": [
-        "cli/kratos", 
-        "cli/kratos-identities", 
-        "cli/kratos-identities-delete", 
-        "cli/kratos-identities-get", 
-        "cli/kratos-identities-import", 
-        "cli/kratos-identities-list", 
-        "cli/kratos-identities-patch", 
-        "cli/kratos-identities-validate", 
-        "cli/kratos-jsonnet", 
-        "cli/kratos-jsonnet-format", 
-        "cli/kratos-jsonnet-lint", 
-        "cli/kratos-remote", 
-        "cli/kratos-remote-status", 
-        "cli/kratos-remote-version"
-      ]
-    }, 
     {
       "items": [
         "cli/kratos", 
diff --git a/go.mod b/go.mod
index 0cd25ca5c..b2df29b11 100644
--- a/go.mod
+++ b/go.mod
@@ -69,6 +69,7 @@ require (
 	github.com/pkg/errors v0.9.1
 	github.com/prometheus/client_golang v1.4.0
 	github.com/prometheus/common v0.9.1
+	github.com/rs/cors v1.6.0
 	github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e
 	github.com/sirupsen/logrus v1.6.0
 	github.com/spf13/cobra v1.0.0
diff --git a/go.sum b/go.sum
index 46c57f876..212024f21 100644
--- a/go.sum
+++ b/go.sum
@@ -1151,6 +1151,7 @@ github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE
 github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rogpeppe/go-internal v1.5.2 h1:qLvObTrvO/XRCqmkKxUlOBc48bI3efyDuAZe25QiF0w=
 github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rs/cors v1.6.0 h1:G9tHG9lebljV9mfp9SNPDL36nCDxmo3zTlAf1YgvzmI=
 github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
 github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
 github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
diff --git a/identity/pool.go b/identity/pool.go
index 5defa61d9..208347a03 100644
--- a/identity/pool.go
+++ b/identity/pool.go
@@ -394,6 +394,24 @@ func TestPool(p PrivilegedPool) func(t *testing.T) {
 			assertEqual(t, expected, actual)
 		})
 
+		t.Run("case=find identity by its credentials case insensitive emails", func(t *testing.T) {
+			expected := passwordIdentity("", "cAsEinSensiTivE@ory.Sh")
+			expected.Traits = Traits(`{}`)
+
+			require.NoError(t, p.CreateIdentity(context.Background(), expected))
+			createdIDs = append(createdIDs, expected.ID)
+
+			actual, creds, err := p.FindByCredentialsIdentifier(context.Background(), CredentialsTypePassword, "CASEINSENSITIVE@ory.Sh")
+			require.NoError(t, err)
+
+			assert.EqualValues(t, expected.Credentials[CredentialsTypePassword].ID, creds.ID)
+			assert.EqualValues(t, []string{"caseinsensitive@ory.sh"}, creds.Identifiers)
+			assert.JSONEq(t, string(expected.Credentials[CredentialsTypePassword].Config), string(creds.Config))
+
+			expected.Credentials = nil
+			assertEqual(t, expected, actual)
+		})
+
 		t.Run("suite=verifiable-address", func(t *testing.T) {
 			createIdentityWithAddresses := func(t *testing.T, email string) VerifiableAddress {
 				var i Identity
diff --git a/internal/httpclient/client/public/complete_self_service_login_flow_with_password_method_parameters.go b/internal/httpclient/client/public/complete_self_service_login_flow_with_password_method_parameters.go
index 5e13742bb..fcf15efea 100644
--- a/internal/httpclient/client/public/complete_self_service_login_flow_with_password_method_parameters.go
+++ b/internal/httpclient/client/public/complete_self_service_login_flow_with_password_method_parameters.go
@@ -14,6 +14,8 @@ import (
 	"github.com/go-openapi/runtime"
 	cr "github.com/go-openapi/runtime/client"
 	"github.com/go-openapi/strfmt"
+
+	"github.com/ory/kratos/internal/httpclient/models"
 )
 
 // NewCompleteSelfServiceLoginFlowWithPasswordMethodParams creates a new CompleteSelfServiceLoginFlowWithPasswordMethodParams object
@@ -60,26 +62,13 @@ for the complete self service login flow with password method operation typicall
 */
 type CompleteSelfServiceLoginFlowWithPasswordMethodParams struct {
 
-	/*CsrfToken
-	  Sending the anti-csrf token is only required for browser login flows.
-
-	*/
-	CsrfToken *string
+	/*Body*/
+	Body *models.CompleteSelfServiceLoginFlowWithPasswordMethod
 	/*Flow
 	  The Flow ID
 
 	*/
 	Flow string
-	/*Identifier
-	  Identifier is the email or username of the user trying to log in.
-
-	*/
-	Identifier *string
-	/*Password
-	  The user's password.
-
-	*/
-	Password *string
 
 	timeout    time.Duration
 	Context    context.Context
@@ -119,15 +108,15 @@ func (o *CompleteSelfServiceLoginFlowWithPasswordMethodParams) SetHTTPClient(cli
 	o.HTTPClient = client
 }
 
-// WithCsrfToken adds the csrfToken to the complete self service login flow with password method params
-func (o *CompleteSelfServiceLoginFlowWithPasswordMethodParams) WithCsrfToken(csrfToken *string) *CompleteSelfServiceLoginFlowWithPasswordMethodParams {
-	o.SetCsrfToken(csrfToken)
+// WithBody adds the body to the complete self service login flow with password method params
+func (o *CompleteSelfServiceLoginFlowWithPasswordMethodParams) WithBody(body *models.CompleteSelfServiceLoginFlowWithPasswordMethod) *CompleteSelfServiceLoginFlowWithPasswordMethodParams {
+	o.SetBody(body)
 	return o
 }
 
-// SetCsrfToken adds the csrfToken to the complete self service login flow with password method params
-func (o *CompleteSelfServiceLoginFlowWithPasswordMethodParams) SetCsrfToken(csrfToken *string) {
-	o.CsrfToken = csrfToken
+// SetBody adds the body to the complete self service login flow with password method params
+func (o *CompleteSelfServiceLoginFlowWithPasswordMethodParams) SetBody(body *models.CompleteSelfServiceLoginFlowWithPasswordMethod) {
+	o.Body = body
 }
 
 // WithFlow adds the flow to the complete self service login flow with password method params
@@ -141,28 +130,6 @@ func (o *CompleteSelfServiceLoginFlowWithPasswordMethodParams) SetFlow(flow stri
 	o.Flow = flow
 }
 
-// WithIdentifier adds the identifier to the complete self service login flow with password method params
-func (o *CompleteSelfServiceLoginFlowWithPasswordMethodParams) WithIdentifier(identifier *string) *CompleteSelfServiceLoginFlowWithPasswordMethodParams {
-	o.SetIdentifier(identifier)
-	return o
-}
-
-// SetIdentifier adds the identifier to the complete self service login flow with password method params
-func (o *CompleteSelfServiceLoginFlowWithPasswordMethodParams) SetIdentifier(identifier *string) {
-	o.Identifier = identifier
-}
-
-// WithPassword adds the password to the complete self service login flow with password method params
-func (o *CompleteSelfServiceLoginFlowWithPasswordMethodParams) WithPassword(password *string) *CompleteSelfServiceLoginFlowWithPasswordMethodParams {
-	o.SetPassword(password)
-	return o
-}
-
-// SetPassword adds the password to the complete self service login flow with password method params
-func (o *CompleteSelfServiceLoginFlowWithPasswordMethodParams) SetPassword(password *string) {
-	o.Password = password
-}
-
 // WriteToRequest writes these params to a swagger request
 func (o *CompleteSelfServiceLoginFlowWithPasswordMethodParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
 
@@ -171,20 +138,10 @@ func (o *CompleteSelfServiceLoginFlowWithPasswordMethodParams) WriteToRequest(r
 	}
 	var res []error
 
-	if o.CsrfToken != nil {
-
-		// query param csrf_token
-		var qrCsrfToken string
-		if o.CsrfToken != nil {
-			qrCsrfToken = *o.CsrfToken
-		}
-		qCsrfToken := qrCsrfToken
-		if qCsrfToken != "" {
-			if err := r.SetQueryParam("csrf_token", qCsrfToken); err != nil {
-				return err
-			}
+	if o.Body != nil {
+		if err := r.SetBodyParam(o.Body); err != nil {
+			return err
 		}
-
 	}
 
 	// query param flow
@@ -196,38 +153,6 @@ func (o *CompleteSelfServiceLoginFlowWithPasswordMethodParams) WriteToRequest(r
 		}
 	}
 
-	if o.Identifier != nil {
-
-		// query param identifier
-		var qrIdentifier string
-		if o.Identifier != nil {
-			qrIdentifier = *o.Identifier
-		}
-		qIdentifier := qrIdentifier
-		if qIdentifier != "" {
-			if err := r.SetQueryParam("identifier", qIdentifier); err != nil {
-				return err
-			}
-		}
-
-	}
-
-	if o.Password != nil {
-
-		// query param password
-		var qrPassword string
-		if o.Password != nil {
-			qrPassword = *o.Password
-		}
-		qPassword := qrPassword
-		if qPassword != "" {
-			if err := r.SetQueryParam("password", qPassword); err != nil {
-				return err
-			}
-		}
-
-	}
-
 	if len(res) > 0 {
 		return errors.CompositeValidationError(res...)
 	}
diff --git a/internal/httpclient/client/public/complete_self_service_settings_flow_with_profile_method_parameters.go b/internal/httpclient/client/public/complete_self_service_settings_flow_with_profile_method_parameters.go
index 9f5faf367..8c7507ba5 100644
--- a/internal/httpclient/client/public/complete_self_service_settings_flow_with_profile_method_parameters.go
+++ b/internal/httpclient/client/public/complete_self_service_settings_flow_with_profile_method_parameters.go
@@ -19,7 +19,7 @@ import (
 // NewCompleteSelfServiceSettingsFlowWithProfileMethodParams creates a new CompleteSelfServiceSettingsFlowWithProfileMethodParams object
 // with the default values initialized.
 func NewCompleteSelfServiceSettingsFlowWithProfileMethodParams() *CompleteSelfServiceSettingsFlowWithProfileMethodParams {
-
+	var ()
 	return &CompleteSelfServiceSettingsFlowWithProfileMethodParams{
 
 		timeout: cr.DefaultTimeout,
@@ -29,7 +29,7 @@ func NewCompleteSelfServiceSettingsFlowWithProfileMethodParams() *CompleteSelfSe
 // NewCompleteSelfServiceSettingsFlowWithProfileMethodParamsWithTimeout creates a new CompleteSelfServiceSettingsFlowWithProfileMethodParams object
 // with the default values initialized, and the ability to set a timeout on a request
 func NewCompleteSelfServiceSettingsFlowWithProfileMethodParamsWithTimeout(timeout time.Duration) *CompleteSelfServiceSettingsFlowWithProfileMethodParams {
-
+	var ()
 	return &CompleteSelfServiceSettingsFlowWithProfileMethodParams{
 
 		timeout: timeout,
@@ -39,7 +39,7 @@ func NewCompleteSelfServiceSettingsFlowWithProfileMethodParamsWithTimeout(timeou
 // NewCompleteSelfServiceSettingsFlowWithProfileMethodParamsWithContext creates a new CompleteSelfServiceSettingsFlowWithProfileMethodParams object
 // with the default values initialized, and the ability to set a context for a request
 func NewCompleteSelfServiceSettingsFlowWithProfileMethodParamsWithContext(ctx context.Context) *CompleteSelfServiceSettingsFlowWithProfileMethodParams {
-
+	var ()
 	return &CompleteSelfServiceSettingsFlowWithProfileMethodParams{
 
 		Context: ctx,
@@ -49,7 +49,7 @@ func NewCompleteSelfServiceSettingsFlowWithProfileMethodParamsWithContext(ctx co
 // NewCompleteSelfServiceSettingsFlowWithProfileMethodParamsWithHTTPClient creates a new CompleteSelfServiceSettingsFlowWithProfileMethodParams object
 // with the default values initialized, and the ability to set a custom HTTPClient for a request
 func NewCompleteSelfServiceSettingsFlowWithProfileMethodParamsWithHTTPClient(client *http.Client) *CompleteSelfServiceSettingsFlowWithProfileMethodParams {
-
+	var ()
 	return &CompleteSelfServiceSettingsFlowWithProfileMethodParams{
 		HTTPClient: client,
 	}
@@ -59,6 +59,15 @@ func NewCompleteSelfServiceSettingsFlowWithProfileMethodParamsWithHTTPClient(cli
 for the complete self service settings flow with profile method operation typically these are written to a http.Request
 */
 type CompleteSelfServiceSettingsFlowWithProfileMethodParams struct {
+
+	/*Payload*/
+	Payload interface{}
+	/*Flow
+	  Flow is flow ID.
+
+	*/
+	Flow *string
+
 	timeout    time.Duration
 	Context    context.Context
 	HTTPClient *http.Client
@@ -97,6 +106,28 @@ func (o *CompleteSelfServiceSettingsFlowWithProfileMethodParams) SetHTTPClient(c
 	o.HTTPClient = client
 }
 
+// WithPayload adds the payload to the complete self service settings flow with profile method params
+func (o *CompleteSelfServiceSettingsFlowWithProfileMethodParams) WithPayload(payload interface{}) *CompleteSelfServiceSettingsFlowWithProfileMethodParams {
+	o.SetPayload(payload)
+	return o
+}
+
+// SetPayload adds the payload to the complete self service settings flow with profile method params
+func (o *CompleteSelfServiceSettingsFlowWithProfileMethodParams) SetPayload(payload interface{}) {
+	o.Payload = payload
+}
+
+// WithFlow adds the flow to the complete self service settings flow with profile method params
+func (o *CompleteSelfServiceSettingsFlowWithProfileMethodParams) WithFlow(flow *string) *CompleteSelfServiceSettingsFlowWithProfileMethodParams {
+	o.SetFlow(flow)
+	return o
+}
+
+// SetFlow adds the flow to the complete self service settings flow with profile method params
+func (o *CompleteSelfServiceSettingsFlowWithProfileMethodParams) SetFlow(flow *string) {
+	o.Flow = flow
+}
+
 // WriteToRequest writes these params to a swagger request
 func (o *CompleteSelfServiceSettingsFlowWithProfileMethodParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
 
@@ -105,6 +136,28 @@ func (o *CompleteSelfServiceSettingsFlowWithProfileMethodParams) WriteToRequest(
 	}
 	var res []error
 
+	if o.Payload != nil {
+		if err := r.SetBodyParam(o.Payload); err != nil {
+			return err
+		}
+	}
+
+	if o.Flow != nil {
+
+		// query param flow
+		var qrFlow string
+		if o.Flow != nil {
+			qrFlow = *o.Flow
+		}
+		qFlow := qrFlow
+		if qFlow != "" {
+			if err := r.SetQueryParam("flow", qFlow); err != nil {
+				return err
+			}
+		}
+
+	}
+
 	if len(res) > 0 {
 		return errors.CompositeValidationError(res...)
 	}
diff --git a/internal/httpclient/client/public/whoami_responses.go b/internal/httpclient/client/public/whoami_responses.go
index c98c93040..7827e6f38 100644
--- a/internal/httpclient/client/public/whoami_responses.go
+++ b/internal/httpclient/client/public/whoami_responses.go
@@ -29,8 +29,8 @@ func (o *WhoamiReader) ReadResponse(response runtime.ClientResponse, consumer ru
 			return nil, err
 		}
 		return result, nil
-	case 403:
-		result := NewWhoamiForbidden()
+	case 401:
+		result := NewWhoamiUnauthorized()
 		if err := result.readResponse(response, consumer, o.formats); err != nil {
 			return nil, err
 		}
@@ -80,28 +80,28 @@ func (o *WhoamiOK) readResponse(response runtime.ClientResponse, consumer runtim
 	return nil
 }
 
-// NewWhoamiForbidden creates a WhoamiForbidden with default headers values
-func NewWhoamiForbidden() *WhoamiForbidden {
-	return &WhoamiForbidden{}
+// NewWhoamiUnauthorized creates a WhoamiUnauthorized with default headers values
+func NewWhoamiUnauthorized() *WhoamiUnauthorized {
+	return &WhoamiUnauthorized{}
 }
 
-/*WhoamiForbidden handles this case with default header values.
+/*WhoamiUnauthorized handles this case with default header values.
 
 genericError
 */
-type WhoamiForbidden struct {
+type WhoamiUnauthorized struct {
 	Payload *models.GenericError
 }
 
-func (o *WhoamiForbidden) Error() string {
-	return fmt.Sprintf("[GET /sessions/whoami][%d] whoamiForbidden  %+v", 403, o.Payload)
+func (o *WhoamiUnauthorized) Error() string {
+	return fmt.Sprintf("[GET /sessions/whoami][%d] whoamiUnauthorized  %+v", 401, o.Payload)
 }
 
-func (o *WhoamiForbidden) GetPayload() *models.GenericError {
+func (o *WhoamiUnauthorized) GetPayload() *models.GenericError {
 	return o.Payload
 }
 
-func (o *WhoamiForbidden) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
+func (o *WhoamiUnauthorized) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
 
 	o.Payload = new(models.GenericError)
 
diff --git a/internal/httpclient/models/complete_self_service_login_flow_with_password_method.go b/internal/httpclient/models/complete_self_service_login_flow_with_password_method.go
new file mode 100644
index 000000000..dd3997615
--- /dev/null
+++ b/internal/httpclient/models/complete_self_service_login_flow_with_password_method.go
@@ -0,0 +1,49 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package models
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+)
+
+// CompleteSelfServiceLoginFlowWithPasswordMethod complete self service login flow with password method
+//
+// swagger:model CompleteSelfServiceLoginFlowWithPasswordMethod
+type CompleteSelfServiceLoginFlowWithPasswordMethod struct {
+
+	// Sending the anti-csrf token is only required for browser login flows.
+	CsrfToken string `json:"csrf_token,omitempty"`
+
+	// Identifier is the email or username of the user trying to log in.
+	Identifier string `json:"identifier,omitempty"`
+
+	// The user's password.
+	Password string `json:"password,omitempty"`
+}
+
+// Validate validates this complete self service login flow with password method
+func (m *CompleteSelfServiceLoginFlowWithPasswordMethod) Validate(formats strfmt.Registry) error {
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *CompleteSelfServiceLoginFlowWithPasswordMethod) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *CompleteSelfServiceLoginFlowWithPasswordMethod) UnmarshalBinary(b []byte) error {
+	var res CompleteSelfServiceLoginFlowWithPasswordMethod
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}
diff --git a/internal/httpclient/models/settings_flow_method.go b/internal/httpclient/models/settings_flow_method.go
index cf38c9a03..7dd55a29d 100644
--- a/internal/httpclient/models/settings_flow_method.go
+++ b/internal/httpclient/models/settings_flow_method.go
@@ -19,7 +19,7 @@ type SettingsFlowMethod struct {
 
 	// config
 	// Required: true
-	Config *FlowMethodConfig `json:"config"`
+	Config *SettingsFlowMethodConfig `json:"config"`
 
 	// Method is the name of this flow method.
 	// Required: true
diff --git a/internal/httpclient/models/flow_method_config.go b/internal/httpclient/models/settings_flow_method_config.go
similarity index 73%
rename from internal/httpclient/models/flow_method_config.go
rename to internal/httpclient/models/settings_flow_method_config.go
index 0ba28b02c..c9b45f818 100644
--- a/internal/httpclient/models/flow_method_config.go
+++ b/internal/httpclient/models/settings_flow_method_config.go
@@ -12,10 +12,10 @@ import (
 	"github.com/go-openapi/validate"
 )
 
-// FlowMethodConfig flow method config
+// SettingsFlowMethodConfig settings flow method config
 //
-// swagger:model FlowMethodConfig
-type FlowMethodConfig struct {
+// swagger:model settingsFlowMethodConfig
+type SettingsFlowMethodConfig struct {
 
 	// Action should be used as the form action URL `<form action="{{ .Action }}" method="post">`.
 	// Required: true
@@ -33,8 +33,8 @@ type FlowMethodConfig struct {
 	Method *string `json:"method"`
 }
 
-// Validate validates this flow method config
-func (m *FlowMethodConfig) Validate(formats strfmt.Registry) error {
+// Validate validates this settings flow method config
+func (m *SettingsFlowMethodConfig) Validate(formats strfmt.Registry) error {
 	var res []error
 
 	if err := m.validateAction(formats); err != nil {
@@ -59,7 +59,7 @@ func (m *FlowMethodConfig) Validate(formats strfmt.Registry) error {
 	return nil
 }
 
-func (m *FlowMethodConfig) validateAction(formats strfmt.Registry) error {
+func (m *SettingsFlowMethodConfig) validateAction(formats strfmt.Registry) error {
 
 	if err := validate.Required("action", "body", m.Action); err != nil {
 		return err
@@ -68,7 +68,7 @@ func (m *FlowMethodConfig) validateAction(formats strfmt.Registry) error {
 	return nil
 }
 
-func (m *FlowMethodConfig) validateFields(formats strfmt.Registry) error {
+func (m *SettingsFlowMethodConfig) validateFields(formats strfmt.Registry) error {
 
 	if err := validate.Required("fields", "body", m.Fields); err != nil {
 		return err
@@ -84,7 +84,7 @@ func (m *FlowMethodConfig) validateFields(formats strfmt.Registry) error {
 	return nil
 }
 
-func (m *FlowMethodConfig) validateMessages(formats strfmt.Registry) error {
+func (m *SettingsFlowMethodConfig) validateMessages(formats strfmt.Registry) error {
 
 	if swag.IsZero(m.Messages) { // not required
 		return nil
@@ -100,7 +100,7 @@ func (m *FlowMethodConfig) validateMessages(formats strfmt.Registry) error {
 	return nil
 }
 
-func (m *FlowMethodConfig) validateMethod(formats strfmt.Registry) error {
+func (m *SettingsFlowMethodConfig) validateMethod(formats strfmt.Registry) error {
 
 	if err := validate.Required("method", "body", m.Method); err != nil {
 		return err
@@ -110,7 +110,7 @@ func (m *FlowMethodConfig) validateMethod(formats strfmt.Registry) error {
 }
 
 // MarshalBinary interface implementation
-func (m *FlowMethodConfig) MarshalBinary() ([]byte, error) {
+func (m *SettingsFlowMethodConfig) MarshalBinary() ([]byte, error) {
 	if m == nil {
 		return nil, nil
 	}
@@ -118,8 +118,8 @@ func (m *FlowMethodConfig) MarshalBinary() ([]byte, error) {
 }
 
 // UnmarshalBinary interface implementation
-func (m *FlowMethodConfig) UnmarshalBinary(b []byte) error {
-	var res FlowMethodConfig
+func (m *SettingsFlowMethodConfig) UnmarshalBinary(b []byte) error {
+	var res SettingsFlowMethodConfig
 	if err := swag.ReadJSON(b, &res); err != nil {
 		return err
 	}
diff --git a/persistence/sql/persister_identity.go b/persistence/sql/persister_identity.go
index c472cb8ad..65df55b80 100644
--- a/persistence/sql/persister_identity.go
+++ b/persistence/sql/persister_identity.go
@@ -54,6 +54,11 @@ func (p *Persister) FindByCredentialsIdentifier(ctx context.Context, ct identity
 		IdentityID uuid.UUID `db:"identity_id"`
 	}
 
+	// Force case-insensitivity for email addresses
+	if strings.Contains(match, "@") && ct == identity.CredentialsTypePassword {
+		match = strings.ToLower(match)
+	}
+
 	if err := p.GetConnection(ctx).RawQuery(`SELECT
     ic.identity_id
 FROM identity_credentials ic
diff --git a/quickstart-oathkeeper.yml b/quickstart-oathkeeper.yml
index a136c1931..929568cdd 100644
--- a/quickstart-oathkeeper.yml
+++ b/quickstart-oathkeeper.yml
@@ -3,7 +3,7 @@ version: '3.7'
 services:
   kratos:
     environment:
-      - SERVE_PUBLIC_BASE_URL=http://127.0.0.1:4433/.ory/kratos/public/
+      - SERVE_PUBLIC_BASE_URL=http://127.0.0.1:4455/.ory/kratos/public/
 
   kratos-selfservice-ui-node:
     environment:
diff --git a/selfservice/flow/settings/flow_method.go b/selfservice/flow/settings/flow_method.go
index a86a7cac6..2672ab0c7 100644
--- a/selfservice/flow/settings/flow_method.go
+++ b/selfservice/flow/settings/flow_method.go
@@ -70,7 +70,7 @@ type FlowMethodConfigurator interface {
 	form.MessageAdder
 }
 
-// swagger:type settingsFlowMethodConfig
+// swagger:model settingsFlowMethodConfig
 type FlowMethodConfig struct {
 	// swagger:ignore
 	FlowMethodConfigurator
diff --git a/selfservice/form/html_form.go b/selfservice/form/html_form.go
index 43ff31ea5..d47380835 100644
--- a/selfservice/form/html_form.go
+++ b/selfservice/form/html_form.go
@@ -179,7 +179,12 @@ func (c *HTMLForm) ParseError(err error) error {
 		default:
 			// The pointer can be ignored because if there is an error, we'll just use
 			// the empty field (global error).
-			for _, ee := range append([]*jsonschema.ValidationError{e}, e.Causes...) {
+			var causes = e.Causes
+			if len(e.Causes) == 0 {
+				causes = []*jsonschema.ValidationError{e}
+			}
+
+			for _, ee := range causes {
 				pointer, _ := jsonschemax.JSONPointerToDotNotation(ee.InstancePtr)
 				c.AddMessage(text.NewValidationErrorGeneric(ee.Message), pointer)
 			}
diff --git a/selfservice/strategy/password/login.go b/selfservice/strategy/password/login.go
index 0ce19f559..4f704b854 100644
--- a/selfservice/strategy/password/login.go
+++ b/selfservice/strategy/password/login.go
@@ -57,7 +57,7 @@ type completeSelfServiceLoginFlowWithPasswordMethodParameters struct {
 	Flow string `json:"flow"`
 
 	// in: body
-	CompleteSelfServiceLoginFlowWithPasswordMethod
+	Body CompleteSelfServiceLoginFlowWithPasswordMethod
 }
 
 // swagger:route POST /self-service/login/methods/password public completeSelfServiceLoginFlowWithPasswordMethod
diff --git a/selfservice/strategy/password/registration.go b/selfservice/strategy/password/registration.go
index d2080e577..3f3bfc14b 100644
--- a/selfservice/strategy/password/registration.go
+++ b/selfservice/strategy/password/registration.go
@@ -99,7 +99,7 @@ type completeSelfServiceRegistrationFlowWithPasswordMethodParameters struct {
 	Flow string `json:"flow"`
 
 	// in: body
-	Payload map[string]interface{}
+	Payload interface{}
 }
 
 // swagger:route POST /self-service/registration/methods/password public completeSelfServiceRegistrationFlowWithPasswordMethod
diff --git a/selfservice/strategy/profile/strategy.go b/selfservice/strategy/profile/strategy.go
index eb5c85ce1..ecb79d719 100644
--- a/selfservice/strategy/profile/strategy.go
+++ b/selfservice/strategy/profile/strategy.go
@@ -82,6 +82,7 @@ func (s *Strategy) SettingsStrategyID() string {
 }
 
 func (s *Strategy) RegisterSettingsRoutes(public *x.RouterPublic) {
+	s.d.CSRFHandler().ExemptPath(RouteSettings)
 	public.POST(RouteSettings, s.d.SessionHandler().IsAuthenticated(s.handleSubmit, settings.OnUnauthenticated(s.c, s.d)))
 	public.GET(RouteSettings, s.d.SessionHandler().IsAuthenticated(s.handleSubmit, settings.OnUnauthenticated(s.c, s.d)))
 }
@@ -117,6 +118,18 @@ func (s *Strategy) PopulateSettingsMethod(r *http.Request, id *identity.Identity
 	return nil
 }
 
+// nolint:deadcode,unused
+// swagger:parameters completeSelfServiceSettingsFlowWithProfileMethod
+type completeSelfServiceSettingsFlowWithProfileMethodParameters struct {
+	// Flow is flow ID.
+	//
+	// in: query
+	Flow string `json:"flow"`
+
+	// in: body
+	Payload interface{}
+}
+
 // swagger:route POST /self-service/settings/methods/profile public completeSelfServiceSettingsFlowWithProfileMethod
 //
 // Complete Settings Flow with Profile Method
diff --git a/session/handler.go b/session/handler.go
index 382784d46..4406451a5 100644
--- a/session/handler.go
+++ b/session/handler.go
@@ -148,7 +148,7 @@ type whoamiParameters struct {
 //
 //     Responses:
 //       200: session
-//       403: genericError
+//       401: genericError
 //       500: genericError
 func (h *Handler) whoami(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
 	s, err := h.r.SessionManager().FetchFromRequest(r.Context(), r)
diff --git a/session/manager_http.go b/session/manager_http.go
index c33fdb2de..787c9baeb 100644
--- a/session/manager_http.go
+++ b/session/manager_http.go
@@ -100,6 +100,10 @@ func (s *ManagerHTTP) extractToken(r *http.Request) string {
 		return token
 	}
 
+	if token := r.Header.Get("X-Session-Token"); len(token) > 0 {
+		return token
+	}
+
 	cookie, err := s.r.CookieManager().Get(r, s.cookieName)
 	if err != nil {
 		return ""
diff --git a/swagger_meta.go b/swagger_meta.go
index c0ff39d05..885f65955 100644
--- a/swagger_meta.go
+++ b/swagger_meta.go
@@ -17,7 +17,7 @@
 //     SecurityDefinitions:
 //     sessionToken:
 //          type: apiKey
-//          name: Authorization
+//          name: X-Session-Token
 //          in: header
 //
 //     Extensions:


Test patch:
diff --git a/internal/testhelpers/selfservice_settings.go b/internal/testhelpers/selfservice_settings.go
index 92f6c2040..b85a0a397 100644
--- a/internal/testhelpers/selfservice_settings.go
+++ b/internal/testhelpers/selfservice_settings.go
@@ -98,14 +98,14 @@ func ExpectURL(isAPI bool, api, browser string) string {
 	return browser
 }
 
-func GetSettingsFlowMethodConfig(t *testing.T, rs *models.SettingsFlow, id string) *models.FlowMethodConfig {
+func GetSettingsFlowMethodConfig(t *testing.T, rs *models.SettingsFlow, id string) *models.SettingsFlowMethodConfig {
 	require.NotEmpty(t, rs.Methods[id])
 	require.NotEmpty(t, rs.Methods[id].Config)
 	require.NotEmpty(t, rs.Methods[id].Config.Action)
 	return rs.Methods[id].Config
 }
 
-func GetSettingsFlowMethodConfigDeprecated(t *testing.T, primaryUser *http.Client, ts *httptest.Server, id string) *models.FlowMethodConfig {
+func GetSettingsFlowMethodConfigDeprecated(t *testing.T, primaryUser *http.Client, ts *httptest.Server, id string) *models.SettingsFlowMethodConfig {
 	rs := InitializeSettingsFlowViaBrowser(t, primaryUser, ts)
 
 	require.NotEmpty(t, rs.Payload.Methods[id])
@@ -228,7 +228,7 @@ func AddAndLoginIdentities(t *testing.T, reg *driver.RegistryDefault, public *ht
 func SettingsMakeRequest(
 	t *testing.T,
 	isAPI bool,
-	f *models.FlowMethodConfig,
+	f *models.SettingsFlowMethodConfig,
 	hc *http.Client,
 	values string,
 ) (string, *http.Response) {
diff --git a/selfservice/strategy/password/login_test.go b/selfservice/strategy/password/login_test.go
index 60c0199d0..404fe5599 100644
--- a/selfservice/strategy/password/login_test.go
+++ b/selfservice/strategy/password/login_test.go
@@ -216,6 +216,7 @@ func TestCompleteLogin(t *testing.T) {
 
 			ensureFieldsExist(t, []byte(body))
 			assert.Equal(t, "Property identifier is missing.", gjson.Get(body, "methods.password.config.fields.#(name==identifier).messages.0.text").String(), "%s", body)
+			assert.Len(t, gjson.Get(body, "methods.password.config.fields").Array(), 3)
 
 			// The password value should not be returned!
 			assert.Empty(t, gjson.Get(body, "methods.password.config.fields.#(name==password).value").String())
@@ -243,6 +244,7 @@ func TestCompleteLogin(t *testing.T) {
 			ensureFieldsExist(t, []byte(body))
 			assert.Equal(t, "Property password is missing.", gjson.Get(body, "methods.password.config.fields.#(name==password).messages.0.text").String(), "%s", body)
 			assert.Equal(t, "identifier", gjson.Get(body, "methods.password.config.fields.#(name==identifier).value").String(), "%s", body)
+			assert.Len(t, gjson.Get(body, "methods.password.config.fields").Array(), 3)
 
 			// This must not include the password!
 			assert.Empty(t, gjson.Get(body, "methods.password.config.fields.#(name==password).value").String())
@@ -262,6 +264,34 @@ func TestCompleteLogin(t *testing.T) {
 		})
 	})
 
+	t.Run("should return an error both identifier and password are missing", func(t *testing.T) {
+		var check = func(t *testing.T, body string) {
+			assert.NotEmpty(t, gjson.Get(body, "id").String(), "%s", body)
+			assert.Contains(t, gjson.Get(body, "methods.password.config.action").String(), publicTS.URL+password.RouteLogin, "%s", body)
+
+			ensureFieldsExist(t, []byte(body))
+			assert.Equal(t, "length must be >= 1, but got 0", gjson.Get(body, "methods.password.config.fields.#(name==password).messages.0.text").String(), "%s", body)
+			assert.Equal(t, "length must be >= 1, but got 0", gjson.Get(body, "methods.password.config.fields.#(name==identifier).messages.0.text").String(), "%s", body)
+			assert.Len(t, gjson.Get(body, "methods.password.config.fields").Array(), 3)
+
+			// This must not include the password!
+			assert.Empty(t, gjson.Get(body, "methods.password.config.fields.#(name==password).value").String())
+		}
+
+		var values = func(v url.Values) {
+			v.Set("password", "")
+			v.Set("identifier", "")
+		}
+
+		t.Run("type=browser", func(t *testing.T) {
+			check(t, expectValidationError(t, false, false, values))
+		})
+
+		t.Run("type=api", func(t *testing.T) {
+			check(t, expectValidationError(t, true, false, values))
+		})
+	})
+
 	t.Run("should return an error because the credentials are invalid (password not correct)", func(t *testing.T) {
 		var check = func(t *testing.T, body string) {
 			assert.NotEmpty(t, gjson.Get(body, "id").String(), "%s", body)
diff --git a/selfservice/strategy/password/settings_test.go b/selfservice/strategy/password/settings_test.go
index bd8598576..053b5932c 100644
--- a/selfservice/strategy/password/settings_test.go
+++ b/selfservice/strategy/password/settings_test.go
@@ -263,7 +263,7 @@ func TestSettings(t *testing.T) {
 		viper.Set(configuration.ViperKeySelfServiceSettingsAfter+"."+configuration.DefaultBrowserReturnURL, rts.URL+"/return-ts")
 		defer viper.Set(configuration.ViperKeySelfServiceSettingsAfter, nil)
 
-		var run = func(t *testing.T, f *models.FlowMethodConfig, isAPI bool, c *http.Client, id *identity.Identity) {
+		var run = func(t *testing.T, f *models.SettingsFlowMethodConfig, isAPI bool, c *http.Client, id *identity.Identity) {
 			values := testhelpers.SDKFormFieldsToURLValues(f.Fields)
 			values.Set("password", randx.MustString(16, randx.AlphaNum))
 			_, res := testhelpers.SettingsMakeRequest(t, isAPI, f, c, testhelpers.EncodeFormAsJSON(t, isAPI, values))
diff --git a/selfservice/strategy/profile/strategy_test.go b/selfservice/strategy/profile/strategy_test.go
index 0c95bc395..6d3b3fef7 100644
--- a/selfservice/strategy/profile/strategy_test.go
+++ b/selfservice/strategy/profile/strategy_test.go
@@ -127,7 +127,7 @@ func TestStrategyTraits(t *testing.T) {
 
 			f := testhelpers.GetSettingsFlowMethodConfig(t, payload, settings.StrategyProfile)
 
-			assertx.EqualAsJSON(t, &models.FlowMethodConfig{
+			assertx.EqualAsJSON(t, &models.SettingsFlowMethodConfig{
 				Action: pointerx.String(publicTS.URL + profile.RouteSettings + "?flow=" + string(payload.ID)),
 				Method: pointerx.String("POST"),
 				Fields: models.FormFields{
@@ -217,7 +217,7 @@ func TestStrategyTraits(t *testing.T) {
 	})
 
 	t.Run("description=should end up at the login endpoint if trying to update protected field without sudo mode", func(t *testing.T) {
-		var run = func(t *testing.T, config *models.FlowMethodConfig, isAPI bool, c *http.Client) *http.Response {
+		var run = func(t *testing.T, config *models.SettingsFlowMethodConfig, isAPI bool, c *http.Client) *http.Response {
 			time.Sleep(time.Millisecond)
 
 			values := testhelpers.SDKFormFieldsToURLValues(config.Fields)
diff --git a/test/e2e/cypress/helpers/index.js b/test/e2e/cypress/helpers/index.js
index 258008f27..8bcee8d7e 100644
--- a/test/e2e/cypress/helpers/index.js
+++ b/test/e2e/cypress/helpers/index.js
@@ -51,7 +51,7 @@ module.exports = {
   gen: {
     email,
     password,
-    identity: () => ({ email: email(), password: password() }),
+    identity: () => ({ email: email(), password: password() })
   },
   assertVerifiableAddress: assertVerifiableAddress,
   assertRecoveryAddress: assertRecoveryAddress,
@@ -64,5 +64,5 @@ module.exports = {
 
   // Adding 1+ second on top because MySQL doesn't do millisecs.
   verifyLifespan: 5000 + 1050,
-  privilegedLifespan: 5000 + 1050,
+  privilegedLifespan: 5000 + 1050
 }
diff --git a/test/e2e/cypress/integration/profiles/email/settings/errors.spec.js b/test/e2e/cypress/integration/profiles/email/settings/errors.spec.js
index 39a2f2788..4d40f66e7 100644
--- a/test/e2e/cypress/integration/profiles/email/settings/errors.spec.js
+++ b/test/e2e/cypress/integration/profiles/email/settings/errors.spec.js
@@ -36,15 +36,6 @@ context('Settings Flow Errors', () => {
     cy.visit(APP_URL + '/settings')
   })
 
-  it('fails when CSRF cookies are missing', () => {
-    cy.clearCookies()
-
-    cy.get('#user-profile button[type="submit"]').click()
-
-    // FIXME https://github.com/ory/kratos/issues/91
-    cy.get('html').should('contain.text', 'CSRF token is missing or invalid')
-  })
-
   describe('profile', () => {
     it('fails with validation errors', () => {
       cy.get('#user-profile input[name="traits.website"]')
diff --git a/test/e2e/cypress/support/commands.js b/test/e2e/cypress/support/commands.js
index 9c952c5b1..32e4721e0 100644
--- a/test/e2e/cypress/support/commands.js
+++ b/test/e2e/cypress/support/commands.js
@@ -32,7 +32,7 @@ import {
   MAIL_API,
   parseHtml,
   pollInterval,
-  privilegedLifespan,
+  privilegedLifespan
 } from '../helpers'
 
 const mergeFields = (form, fields) => {
@@ -55,7 +55,7 @@ Cypress.Commands.add(
 
     cy.request({
       url: APP_URL + '/self-service/registration/browser',
-      followRedirect: false,
+      followRedirect: false
     })
       .then(({ redirectedToUrl }) => {
         expect(redirectedToUrl).to.contain(APP_URL + '/auth/registration?flow=')
@@ -75,10 +75,10 @@ Cypress.Commands.add(
           body: mergeFields(form, {
             ...fields,
             'traits.email': email,
-            password,
+            password
           }),
           url: form.action,
-          followRedirect: false,
+          followRedirect: false
         })
       })
       .then((res) => {
@@ -100,7 +100,7 @@ Cypress.Commands.add(
     rememberConsent = true,
     acceptLogin = true,
     acceptConsent = true,
-    expectSession = true,
+    expectSession = true
   }) => {
     cy.visit(APP_URL + '/auth/registration')
 
@@ -168,7 +168,7 @@ Cypress.Commands.add('login', ({ email, password, expectSession = true }) => {
 
   cy.request({
     url: APP_URL + '/self-service/login/browser',
-    followRedirect: false,
+    followRedirect: false
   })
     .then(({ redirectedToUrl }) => {
       expect(redirectedToUrl).to.contain(APP_URL + '/auth/login?flow=')
@@ -182,7 +182,7 @@ Cypress.Commands.add('login', ({ email, password, expectSession = true }) => {
         method: form.method,
         body: mergeFields(form, { identifier: email, password }),
         url: form.action,
-        followRedirect: false,
+        followRedirect: false
       })
     })
     .then((res) => {
@@ -208,7 +208,7 @@ Cypress.Commands.add(
   'reauth',
   ({
     expect: { email },
-    type: { email: temail, password: tpassword } = {},
+    type: { email: temail, password: tpassword } = {}
   }) => {
     cy.url().should('include', '/auth/login')
     cy.get('input[name="identifier"]').should('have.value', email)
@@ -272,7 +272,7 @@ Cypress.Commands.add('noSession', () =>
     .request({
       method: 'GET',
       url: `${KRATOS_PUBLIC}/sessions/whoami`,
-      failOnStatusCode: false,
+      failOnStatusCode: false
     })
     .then((request) => {
       expect(request.status).to.eq(401)
@@ -284,7 +284,7 @@ Cypress.Commands.add('getIdentityByEmail', ({ email }) =>
     .request({
       method: 'GET',
       url: `${KRATOS_ADMIN}/identities`,
-      failOnStatusCode: false,
+      failOnStatusCode: false
     })
     .then((response) => {
       expect(response.status).to.eq(200)
diff --git a/test/schema/schema_test.go b/test/schema/schema_test.go
index c00c8475b..d6ee29f8c 100644
--- a/test/schema/schema_test.go
+++ b/test/schema/schema_test.go
@@ -3,7 +3,6 @@ package schema
 import (
 	"bytes"
 	"fmt"
-	"github.com/pkg/errors"
 	"io"
 	"io/ioutil"
 	"os"
@@ -11,6 +10,8 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/pkg/errors"
+
 	"github.com/ghodss/yaml"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"