diff --git a/docs/docs/self-service/flows/user-login-user-registration.mdx b/docs/docs/self-service/flows/user-login-user-registration.mdx index f162c381b7f..b021128cb99 100644 --- a/docs/docs/self-service/flows/user-login-user-registration.mdx +++ b/docs/docs/self-service/flows/user-login-user-registration.mdx @@ -5,59 +5,127 @@ sidebar_label: Overview --- import Mermaid from '@theme/Mermaid' +import Tabs from '@theme/Tabs' +import TabItem from '@theme/TabItem' +import SelfServiceBrowserFlow from '@theme/SelfServiceBrowserFlow' +import SelfServiceAPIFlow from '@theme/SelfServiceAPIFlow' +import CodeFromRemote from '@theme/CodeFromRemote' -ORY Kratos supports two type of login and registration flows: +There are two login and registration flow types supported in ORY Kratos: -- Browser-based (easy): This flow works for all applications running on top of a - browser. Websites, single-page apps, Cordova/Ionic, and so on. -- API-based (advanced): This flow works for native applications like iOS - (Swift), Android (Java), Microsoft (.NET), React Native, Electron, and others. +- Flows where the user sits in front of the Browser (e.g. website, single page + app, ...) +- Flows where API interaction is required (e.g. mobile app, Smart TV, ...) The login and registration flows documented here are the foundation for the [password](user-login-user-registration/username-email-password.mdx) and [social](user-login-user-registration/openid-connect-social-sign-in-oauth2.mdx) sign in and sign up strategies. +:::note + +Please read the [Self-Service Flows](../../self-service) overview before continuing +with this document. + +::: + +The Login and Registration User Flow is composed of several high-level steps +summarized in this state diagram: + + s1 + s1 --> s2 : User clicks "Log in / Sign up" + s2 --> Error : A hook fails + s2 --> s3 : User is redirected to Login/Registration UI URL + s3 --> s4 : User provides valid credentials/registration data + s3 --> s5 : User provides invalid credentials/registration data + s5 --> s3 : User is redirected to Login/Registration UI URL + s4 --> Error : A Hook fails + s4 --> s6 + s6 --> [*] + Error --> [*] +`} +/> + ## Self-Service User Login and User Registration for Browser Applications -ORY Kratos supports browser applications that run on server-side (e.g. Java, -NodeJS, PHP) as well as client-side (e.g. JQuery, ReactJS, AngularJS, ...). +Each Login and Registration Method (e.g. +[Username and Password](user-login-user-registration/username-email-password.mdx), +[Social Sign In](user-login-user-registration/openid-connect-social-sign-in-oauth2.mdx), +Passwordless, ...) works a bit different but they all boil down to the same +abstract sequence. This sequence was already established in the [Self-Service Browser Flow Documentation](../../self-service#browser-flows). + + + +The flow payload and the form data the browser sends depend on the concrete flow (registration, login) +and method ([Username and Password](user-login-user-registration/username-email-password.mdx), +[Social Sign In](user-login-user-registration/openid-connect-social-sign-in-oauth2.mdx)). + +The _Flow UI_ (**your application!**) is responsible for +rendering the actual Login and Registration HTML Forms. You can of course +implement one app for rendering all the Login, Registration, ... screens, and +another app (think "Service Oriented Architecture", "Micro-Services" or "Service +Mesh") is responsible for rendering your Dashboards, Management Screens, and so +on. -Browser-based login and registration makes use of three core HTTP technologies: +Let us look at the three steps such a flow performs: -- HTTP Redirects -- HTTP POST (`application/json`, `application/x-www-urlencoded`) and RESTful GET - requests. -- HTTP Cookies to prevent CSRF and Session Hijaking attack vectors. +1. Initialization and redirect to UI; +2. Login and Registration Form Rendering; +3. Form submission and payload validation. -The browser flow is the easiest and most secure to set up and integrated with. -ORY Kratos takes care of all required session and CSRF cookies and ensures that -all security requirements are fulfilled. +### Initialization and Redirect to UI -Future versions of ORY Kratos will be able to deal with multi-domain -environments that require SSO. For example, one account would be used to sign -into both `mydomain.com` and `anotherdomain.org`. A common real-world example is -using your Google account to seamlessly be signed into YouTube and Google at the -same time. +The Browser [calls the initialization endpoint](../../self-service#initialization-and-redirect-to-ui) for +login (`/self-service/login/browser`) or registration (`/self-service/login/browser`) +and is redirected to the UI configured at -This flow is not suitable for scenarios where you use purely programmatic -clients that do not work well with HTTP Cookies and HTTP Redirects. +```yaml title="path/to/config/kratos.yml" +selfservice: + flows: + login: + ui_url: http://... + registration: + ui_url: http://... +``` -### The Login and Registration User Interface +### Login and Registration Form Rendering The Login and Registration User Interface is a route (page / site) in your -application that should render a sign in and registration form: +application (both server side application or single page app) +that should render a sign in and registration form: + + + ```html -
+``` + +
+ - +```html
@@ -72,442 +140,183 @@ application that should render a sign in and registration form:
``` -Once implemented (using e.g. our -[reference implementation](https://github.com/ory/kratos-selfservice-ui-node)) -you reference these routes in your ORY Kratos config file: +
+
-```yaml title="path/to/config/kratos.yml" -# ... +Depending on the type of login flows you want to support, you may also add a +"Sign up/in with GitHub" flow: -urls: - login_ui: https://.../login - registration_ui: https://.../registration -# ... + + + +```html +
+ + + Sign in with: + + + +
``` -Depending on the type of login flows you want to support, you may also add a -"Sign up/in with GitHub" flow: +
+ ```html -
- + Sign up with: + +
``` +
+
+ In stark contrast to other Identity Systems, ORY Kratos does not render this HTML. Instead, you need to implement the HTML code in your application (e.g. NodeJS + ExpressJS, Java, PHP, ReactJS, ...), which gives you extreme flexibility and customizability in your user interface flows and designs. -Each Login and Registration Method (e.g. -[Username and Password](user-login-user-registration/username-email-password.mdx), -[Social Sign In](user-login-user-registration/openid-connect-social-sign-in-oauth2.mdx), -Passwordless, ...) works a bit different but they all boil down to the same -abstract sequence: - ->K: Initiate Login Flow - K->>B: Redirects to your Application's /login?flow= endpoint - B->>A: Calls /login?flow= - A-->>K: Fetches data to render forms using ?flow= - B-->>A: Fills out forms, clicks e.g. "Submit Login" - B->>K: POSTs data to - K-->>K: Processes Login Info - alt Login data valid - K-->>B: Sets session cookie - K->>B: Redirects to e.g. Dashboard - else Login data invalid - K-->>B: Redirects to your Applicaiton's /login endpoint - B->>A: Calls /login - A-->>K: Fetches data to render form fields and errors - B-->>A: Fills out forms again, corrects errors - B->>K: POSTs data again - and so on... - end -`} -/> - -The exact data being fetched and the step _"Processes Login / Registration -Info"_ depend, of course, on the actual Login / Registration Method being used. -But it is important to understand that **"Your Application"** is responsible for -rendering the actual Login and Registration HTML Forms. You can of course -implement one app for rendering all the Login, Registration, ... screens, and -another app (think "Service Oriented Architecture", "Micro-Services" or "Service -Mesh") is responsible for rendering your Dashboards, Management Screens, and so -on. - -:::note - -It is RECOMMENDED to put all applications (or "services"), including ORY Kratos, -behind a common API Gateway or Reverse Proxy. This greatly reduces the amount of -work you have to do to get all the Cookies working properly. We RECOMMEND using -[ORY Oathkeeper](http://github.com/ory/oathkeeper) for this as it integrates -best with the ORY Ecosystem and because all of our examples use ORY Oathkeeper. -You MAY of course use any other reverse proxy (Envoy, AWS API Gateway, -Ambassador, Nginx, Kong, ...), but we do not have examples or guides for those -at this time. - -::: - -### Code +### Code Examples Because Login and Registration are so similar, we can use one common piece of code to cover both. A functioning example of the code and approach used here can be found on [github.com/ory/kratos-selfservice-ui-node](https://github.com/ory/kratos-selfservice-ui-node). -The code example used here is universal and does not use an SDK because we want -you to understand the fundamentals of how this flow works. - -#### Server-side route - -While this example assumes a Server-Side Application, a Client-Side (e.g. -ReactJS) Application would work the same, but use ORY Kratos' Public API -instead. - -```js -// Uses the ORY Kratos NodeJS SDK - for more SDKs check: -// -// https://www.ory.sh/kratos/docs/sdk/index -const { AdminApi } = require('@oryd/kratos-client') - -// The browser config key is used to redirect the user. It reflects where ORY Kratos' Public API -// is accessible from. Here, we're assuming traffic going to `http://example.org/.ory/kratos/public/` -// will be forwarded to ORY Kratos' Public API. -const kratosBrowserUrl = 'http://example.org/.ory/kratos/public/' - -// Initializes the SDK with ORY Kratos' Admin API. -const adminEndpoint = new AdminApi('https://ory-kratos-admin.example-org.vpc/') - -// The parameter "flow" can be "login" and "registration". -// You would register the two routes in express js like this: -// -// app.get('/auth/registration', authHandler('registration')) -// app.get('/auth/login', authHandler('login')) -// -export const authHandler = (flow) => (req, res, next) => { - // The request ID is used to identify the login and registraion request and - // return data like the csrf_token and so on. - const flowId = req.query.flow - if (!flowId) { - console.log(`No request found in URL, initializing ${flow} flow.`) - res.redirect(`${kratosBrowserUrl}/self-service/${flow}/browser`) - return - } - - const authRequest = - flow === 'login' - ? adminEndpoint.getSelfServiceLoginFlow(flowId) - : adminEndpoint.getSelfServiceRegistrationFlow(flowId) - - authRequest.then(({ body, response }) => { - if (response.statusCode !== 200) { - res.redirect( - `${config.kratos.browser}/self-service/browser/flows/${flow}` - ) - return - } - - // "body" contains all the request data for this Registration request. - // You can process that data here, if you want. - - // only show the form section that is "active" to avoid user confusion. - let password = body.methods.password.config - let oidc = body.methods.oidc.config - switch (body.active) { - case 'password': - oidc = undefined // if password is active hide this - break - case 'oidc': - password = undefined // if oidc is active hide this - break - } - - // Lastly, you probably want to render the data using a view (e.g. Jade Template): - res.render(flow, { oidc, password }) - }) - // Handle errors using ExpressJS' next functionality: - // - // .catch(next) -} -``` + + -#### Views + -Your views can be rather simple, as ORY Kratos provides you with all the -information you need for rendering the forms. The following example illustrates -a generic form generator (we use handlebars here) that works with ORY Kratos: +The views can be rather simple, as ORY Kratos provides you with all the +information you need for rendering the forms. -```handlebars name="views/partials/form.hbs" -{{~#each messages~}} - -
{{text}}
-{{~/each~}} +The following examples use Handlebars and a generic form generator to render the Flow: -
- {{~#each form.messages~}} - -
{{text}}
- {{~/each~}} + - {{#each form.fields}} - {{~#each messages~}} - -
{{text}}
- {{~/each~}} + - - {{/each}} + - + + - - -``` + -In your main "Login" or "Sign Up" view you would then consume this template for -all the methods you want to support: + -```handlebars name="views/login_or_signup.hbs" - -{{#if password}} - {{> form form=password}} -{{/if}} + - -{{#if oidc}} - {{> form form=oidc}} -{{/if}} + +
- - -``` +The rest of the form partials can be found +[here](https://github.com/ory/kratos-selfservice-ui-node/tree/master/views/partials). -For details on payloads and potential HTML snippets consult the individual +
+ + +A react example is currently in the making. + + +
+ +For details on payloads consult the individual Self-Service Strategies for: - [Username and Password Strategy](user-login-user-registration/username-email-password.mdx) - [Social Sign In Strategy](user-login-user-registration/openid-connect-social-sign-in-oauth2.mdx) -### Server-Side Browser Applications +## Self-Service User Login and User Registration for API Clients -Let's take a look at the concrete network topologies, calls, and payloads. Here, -we're assuming that you're running a server-side browser application (written in -e.g. PHP, Java, NodeJS) to render the login and registration screen on the -server and make all API calls from that server code. The counterpart to this -would be a client-side browser application (written in e.g. Vanilla JavaScript, -JQuery, ReactJS, AngularJS, ...) that uses AJAX requests to fetch data. For -these type of applications, read this section first and go to section -[Client-Side Browser Applications](#client-side-browser-applications) next. +Each Login and Registration Method (e.g. +[Username and Password](user-login-user-registration/username-email-password.mdx), +[Social Sign In](user-login-user-registration/openid-connect-social-sign-in-oauth2.mdx), +Passwordless, ...) works a bit different but they all boil down to the same +abstract sequence. This sequence was already established in the [Self-Service API Flow Documentation](../../self-service#api-flows). -#### Network Architecture + -We recommend checking out the -[Quickstart Network Architecture](../../quickstart.mdx#network-architecture) for -a high-level, exemplary, overview of the network. In summary: +The flow payload and the form data the browser sends depend on the concrete flow (registration, login) +and method ([Username and Password](user-login-user-registration/username-email-password.mdx), +[Social Sign In](user-login-user-registration/openid-connect-social-sign-in-oauth2.mdx)). -1. The SecureApp (your application) is exposed at http://127.0.0.1:4455 and - proxies requests matching path `./ory/kratos/public/*` to ORY Krato's Public - API Port. -1. ORY Kratos exposes (for debugging only!!) the Public API at - http://127.0.0.1:4433 and Admin API at http://127.0.0.1:4434. -1. Within the "intranet" or "private network", ORY Kratos is exposed at - http://kratos:4433 and http://kratos:4434. These URLs are be used by the - SecureApp to communicate with ORY Kratos. +Let us look at the three steps such a flow performs: -Keep in mind that his architecture is just one of many possible network -architectures. It is however one of the simplest as well and it works locally. -For production deployments you would probably use an Reverse Proxy such as -Nginx, Kong, Envoy, ORY Oathkeeper, or others. +1. Initialization; +2. Login and Registration Form Rendering; +3. Form submission and payload validation. -#### User Login and User Registration Process Sequence +### Initialization -The Login and Registration User Flow is composed of several high-level steps -summarized in this state diagram: +The client [calls the API-flow initialization endpoint](../../self-service#initialization) for +login (`/self-service/login/api`) or registration (`/self-service/login/api`). The response +is a JSON payload containing information about the flow. - s1 - s1 --> s2 : User clicks "Log in / Sign up" - s2 --> Error : A hook fails - s2 --> s3 : User is redirected to Login/Registration UI URL - s3 --> s4 : User provides valid credentials/registration data - s3 --> s5 : User provides invalid credentials/registration data - s5 --> s3 : User is redirected to Login/Registration UI URL - s4 --> Error : A Hook fails - s4 --> s6 - s6 --> [*] - Error --> [*] -`} -/> +### Login and Registration Form Rendering -1. The **Login/Registration User Flow** is initiated because a link was clicked - or an action was performed that requires an active user session. -1. ORY Kratos executes Jobs defined in the **Before Login/Registration - Workflow**. If a failure occurs, the whole flow is aborted. -1. The user's browser is redirected to - `http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/(login|registration)` - (the notation `(login|registration)` expresses the two possibilities of - `../self-service/login/browser` or - `../self-service/browser/flows/registration`). -1. ORY Kratos does some internal processing (e.g. checks if a session cookie is - set, generates payloads for form fields, sets CSRF token, ...) and redirects - the user's browser to the Login UI URL which is defined using the - `selfservice.flows.login.ui_url` (or `selfservice.flows.registration.ui_url`) - config or `SELFSERVICE_FLOWS_LOGIN_UI_URL` (or - `SELFSERVICE_FLOWS_REGISTRATION_UI_URL`) environment variable, which is set - to the ui endpoints - for example `https://127.0.0.1:4455/auth/login` and - `https://127.0.0.1:4455/auth/registration`). The user's browser is thus - redirected to `https://127.0.0.1:4455/auth/(login|registration)?flow=abcde`. - The `request` query parameter includes a unique ID which will be used to - fetch contextual data for this login request. -1. Your Server-Side Application makes a `GET` request to - `http://kratos:4434/self-service/browser/flows/requests/(login|registration)?flow=abcde`. - ORY Kratos responds with a JSON Payload that contains data (form fields, - error messages, ...) for all enabled User Login Strategies: - ```json5 - { - id: 'abcde', - methods: { - password: { method: 'password', config: { action: '...', fields: [] } }, - oidc: { method: 'oidc', config: { action: '...', fields: [] } } - } - } - ``` -1. Your Server-Side applications renders the data however you see fit. The User - interacts with it an completes the Login by clicking, for example, the - "Login", the "Login with Google", ... button. -1. The User's browser makes a request to one of ORY Kratos' Strategy URLs (e.g. - `http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/strategies/password/(login|registration)` - or - `https://127.0.0.1:4455/.ory/kratos/public/self-service/browser/strategies/oidc/auth/abcde`). - ORY Kratos validates the User's credentials (when logging in - e.g. Username - and Password, by performing an OpenID Connect flow, ...) or the registration - form data (when signing up - e.g. is the E-Mail address valid, is the person - at least 21 years old, ...): - - If the credentials / form data is invalid, the Login Request's JSON Payload - is updated - for example with - ```json5 - { - id: 'abcde', - methods: { - oidc: { - method: 'oidc', - config: { - /* ... */ - } - }, - password: { - method: 'password', - config: { - /* ... */ - messages: [ - { - id: 4000006, - text: 'The provided credentials are invalid. Check for spelling mistakes in your password or username, email address, or phone number.', - type: 'error' - } - ] - } - } - } - } - ``` - and the user's Browser is redirected back to the Login UI: - `https://127.0.0.1:4455/auth/(login|registration)?flow=abcde`. - - If credentials / data is valid, ORY Kratos proceeds with the next step. - - If the flow is a registration flow and the registration data is valid, the - identity is created. -1. ORY Kratos executes hooks defined in the **After Login/Registration Hooks**. - If a failure occurs, the whole flow is aborted. -1. The client receives the expected response. For browsers, this is a HTTP - Redirection, for API clients it will be a JSON response containing the - session and/or identity. For more information on this topic check - [Self-Service Flow Completion](../../concepts/browser-redirect-flow-completion.mdx). +The form is rendered as established in the [API flow form rendering documentation](../../self-service#form-rendering-1). ->+A: GET /.ory/kratos/public/self-service/browser/flows/(login|registration) - A->>+KP: GET /self-service/login/browser(login|registration) - KP-->>KP: Execute Hooks defined in "Before Login/Registration" - KP-->>-A: HTTP 302 Found /auth/(login|registration)?flow=abcde - A-->>-B: HTTP 302 Found /auth/(login|registration)?flow=abcde - B->>+A: GET /auth/(login|registration)?flow=abcde - A->>+KA: GET/self-service/browser/(login|registration)/flows?id=abcde - KA->>-A: Sends Login/Registration Flow JSON Payload - Note over A,KA: {"methods":{"password":...,"oidc":..}} - A-->>A: Generate and render HTML - A-->>-B: Return HTML (Form, ...) - B-->>B: Fill out HTML - B->>+KP: POST HTML Form - KP-->>KP: Checks login / registration data - alt Login data is valid - KP-->>-KP: Execute Jobs defined in "After Login Workflow(s)" - KP-->>A: HTTP 302 Found /dashboard - Note over KP,B: Set-Cookie: auth_session=... - B->>+A: GET /dashboard - A-->KA: Validates Session Cookie - A->>-B: Send Dashboard Response - else Login data is invalid - Note over KP,B: User retries login / registration - KP-->>B: HTTP 302 Found /auth/(login|registration)?flow=abcde - end -`} -/> +### Form Submission -### Client-Side Browser Applications +To submit the form, follow [API flow form submission and payload validation](../../self-service#form-submission-and-payload-validation-1). -Because Client-Side Browser Applications do not have access to ORY Kratos' Admin -API, they must use the ORY Kratos Public API instead. The flow for a Client-Side -Browser Application is almost the exact same as the one for Server-Side -Applications, with the small difference that -`https://127.0.0.1:4455/.ory/kratos/public/self-service/login/flows?id=abcde` -would be called via AJAX instead of making a request to -`https://kratos:4434/self-service/login/flows?id=abcde`. +### Code Examples -To prevent brute force, guessing, session injection, and other attacks, it is -required that cookies are working for this endpoint. The cookie set in the -initial HTTP request made to -`https://127.0.0.1:4455/.ory/kratos/public/self-service/login/browser` MUST be -set and available when calling this endpoint! +To be included soon. -:::info + + -The initialization request -(`http://127.0.0.1:4455/.ory/kratos/public/self-service/(login|registration)/browser`) -**cannot** be made via AJAX or API requests. You **must** open that URL in the -user's browser using e.g. `window.open` `location.href` or plain and simple old -``. +Code samples will follow soon -::: + + -## Self-Service User Login and User Registration for API Clients +Code samples will follow soon -Will be addressed in a future release. + + ## Hooks @@ -515,8 +324,7 @@ ORY Kratos allows you to configure hooks that run before and after a Login or Registration Request is generated. This may be helpful if you'd like to restrict logins to IPs coming from your internal network or other logic. -For more information about hooks please read the -[Hook Documentation](../hooks/index.mdx). +For more information about hooks please read the [Hook Documentation](../hooks). ## Refreshing a Session diff --git a/docs/docs/self-service/flows/user-login-user-registration/username-email-password.mdx b/docs/docs/self-service/flows/user-login-user-registration/username-email-password.mdx index 40691e48eb4..48fabc953a3 100644 --- a/docs/docs/self-service/flows/user-login-user-registration/username-email-password.mdx +++ b/docs/docs/self-service/flows/user-login-user-registration/username-email-password.mdx @@ -9,16 +9,28 @@ Please read the [Username / Email & Password Credentials Documentation](../../../concepts/credentials/username-email-password.mdx) and [User Login and Registration Documentation](../user-login-user-registration.mdx) +and +[Self-Service Flows Documentation](../../../self-service) first. ::: +import CodeFromRemote from '@theme/CodeFromRemote' + This document summarizes exemplary request payloads for performing "login or sign up with email/username and password" flows using the user login and registration flow with the `password` strategy. +We will use the following JSON Schema in all examples on this page: + + + ## Browser Clients +Browser clients are server-side applications that run in a browser, or Single Page Apps (ReactJS, AngularJS, ...). + ### Registration Redirecting the browser to the @@ -28,88 +40,50 @@ Request Response Payload will include a `password` method. ORY Kratos uses the Identity JSON Schema defined in `identity.default_schema_url` to generate a list of form fields and add it to -the Registration Request. Using an Identity JSON Schema like +the Registration Request. -```json title="my/identity.schema.json" -{ - "$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Person", - "type": "object", - "properties": { - "traits": { - "type": "object", - "properties": { - "email": { - "type": "string", - "format": "email", - "title": "E-Mail", - "minLength": 3, - "ory.sh/kratos": { - "credentials": { - "password": { - "identifier": true - } - } - } - }, - "name": { - "type": "object", - "properties": { - "first": { - "type": "string" - }, - "last": { - "type": "string" - } - } - } - }, - "required": ["email"], - "additionalProperties": false - } - } -} -``` +The identity schema above will result in the following Registration Request -will result in the following Registration Request +```shell script +$ curl -H "Accept: application/json" -s \ + 'http://127.0.0.1:4434/self-service/registration/flows?id=d5c78fd6-17e7-45e2-9465-0200c76bf83a' | jq -```json5 title="$ curl http:///self-service/browser/flows/requests/registration?request=713df601-d6c8-4331-8195-c29b92db459f" { - id: '713df601-d6c8-4331-8195-c29b92db459f', - expires_at: '2020-01-27T16:31:00.3507956Z', - issued_at: '2020-01-27T16:21:00.3508076Z', - request_url: 'http://127.0.0.1:4455/auth/browser/registration', - methods: { - password: { - method: 'password', - config: { - action: 'http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/registration/strategies/password?request=713df601-d6c8-4331-8195-c29b92db459f', - method: 'POST', - fields: [ - { - name: 'csrf_token', - type: 'hidden', - required: true, - value: '0klCuilgIO2k0Ev3J3IEsMOlmxg0RhjiiiWXVKm3Pd7HxZLVkDHWoOSfiT+/BJn69Dg2fmq6MHv8HkEx6MrVlw==' + "id": "d5c78fd6-17e7-45e2-9465-0200c76bf83a", + "type": "browser", + "expires_at": "2020-08-24T11:58:06.0780652Z", + "issued_at": "2020-08-24T11:48:06.0780828Z", + "request_url": "http://127.0.0.1:4433/self-service/registration/browser", + "messages": null, + "methods": { + "password": { + "method": "password", + "config": { + "action": "http://127.0.0.1:4433/self-service/registration/methods/password?flow=d5c78fd6-17e7-45e2-9465-0200c76bf83a", + "method": "POST", + "fields": [ + { + "name": "csrf_token", + "type": "hidden", + "required": true, + "value": "NeScRZ0rccVGilfS2dvlSjDnYk/EvhpozPKW3TBeAnpCArwCFt2La1xbxeFFnmFycdg1LngGTeA13HJ1XCccQA==" }, { - name: 'traits.email', - type: 'email', - required: true + "name": "password", + "type": "password", + "required": true }, { - name: 'password', - type: 'password', - required: true + "name": "traits.email", + "type": "email" }, { - name: 'traits.name.first', - type: 'text' + "name": "traits.name.first", + "type": "text" }, { - name: 'traits.name.last', - type: 'text' + "name": "traits.name.last", + "type": "text" } ] } @@ -139,39 +113,61 @@ redirected to the same login endpoint with the same request ID. When fetching that request ID again, error details will be included in the JSON Response, such as: -```json5 title="$ curl http:///self-service/browser/flows/requests/registration?request=713df601-d6c8-4331-8195-c29b92db459f" +```shell script +$ curl -H "Accept: application/json" -s \ + 'http://127.0.0.1:4434/self-service/registration/flows?id=d5c78fd6-17e7-45e2-9465-0200c76bf83a' | jq + { - id: '713df601-d6c8-4331-8195-c29b92db459f', - expires_at: '2020-01-27T16:31:00.3507956Z', - issued_at: '2020-01-27T16:21:00.3508076Z', - request_url: 'http://127.0.0.1:4433/self-service/browser/flows/registration', - active: 'password', // <- this is now set - methods: { - password: { - method: 'password', - config: { - messages: [ - { - /* id, type */ - text: 'Please update the Form Fields to proceed.' - } - ], - action: 'http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/registration/strategies/password?request=713df601-d6c8-4331-8195-c29b92db459f', - method: 'POST', - fields: [ - /* ... */ - { - name: 'password', - type: 'text', - value: 't4aegbydfv5234', - messages: [ + "id": "d5c78fd6-17e7-45e2-9465-0200c76bf83a", + "type": "browser", + "expires_at": "2020-08-24T11:58:06.0780652Z", + "issued_at": "2020-08-24T11:48:06.0780828Z", + "request_url": "http://127.0.0.1:4433/self-service/registration/browser", + "active": "password", + "messages": null, + "methods": { + "password": { + "method": "password", + "config": { + "action": "http://127.0.0.1:4433/self-service/registration/methods/password?flow=d5c78fd6-17e7-45e2-9465-0200c76bf83a", + "method": "POST", + "fields": [ + { + "name": "csrf_token", + "type": "hidden", + "required": true, + "value": "9ffVGnARIis5qPaS8JGoaNLHgxODXlkDq00GE+XK+fGCEfVd++fYhSN5ZKFs1CxQk/jUcj/mDotSY+K7ibPnyw==" + }, + { + "name": "password", + "type": "password", + "required": true, + "messages": [ { - /* id, type */ - text: "traits.email: Does not match format 'email'" + "id": 4000002, + "text": "Property password is missing.", + "type": "error", + "context": { + "property": "password" + } } ] + }, + { + "name": "traits.email", + "type": "text", + "value": "" + }, + { + "name": "traits.name.first", + "type": "text", + "value": "" + }, + { + "name": "traits.name.last", + "type": "text", + "value": "" } - /* ... */ ] } } @@ -179,10 +175,10 @@ as: } ``` -> Validation error messages and context will be improved in future releases. -> This is tracked as [kratos#185](https://github.com/ory/kratos/issues/185). +If the form is valid, ORY Kratos will create the user and respond with a HTTP 302 redirect +to the configured redirect URL. -The flow completes otherwise. +Unless the `session` hook is configured, no session cookie will be included in the `Set-Cookie` HTTP header. ### Login @@ -193,76 +189,231 @@ Response Payload will include a `password` method. In contrast to the Registration sequence, this payload does not change when the Identity JSON Schema changes: -```json5 title="$ curl http:///self-service/browser/flows/requests/login?request=0cfb0f7e-3866-453c-9c23-28cc2cb7fead" +```shell script +$ curl -H "Accept: application/json" -s \ + 'http://127.0.0.1:4434/self-service/login/flows?id=7c4260f5-55ad-470a-b1d4-e72dd011e4fe' | jq + { - id: '0cfb0f7e-3866-453c-9c23-28cc2cb7fead', - expires_at: '2020-01-27T16:48:53.8826084Z', - issued_at: '2020-01-27T16:38:53.8826392Z', - request_url: 'http://127.0.0.1:4433/self-service/browser/flows/login', - methods: { - password: { - method: 'password', - config: { - action: 'http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/login/strategies/password?request=0cfb0f7e-3866-453c-9c23-28cc2cb7fead', - method: 'POST', - fields: [ - { - name: 'csrf_token', - type: 'hidden', - required: true, - value: 'F0LABRxm/os+18VBUcbmz98LkJid1sEj++4X41rcdbcCzhBqpTcIxn6YB4nJsHuF6JY9/sMq6bqN1cGGG6Gd/g==' + "id": "7c4260f5-55ad-470a-b1d4-e72dd011e4fe", + "type": "browser", + "expires_at": "2020-08-24T12:01:46.0750749Z", + "issued_at": "2020-08-24T11:51:46.0750749Z", + "request_url": "http://127.0.0.1:4433/self-service/login/browser", + "messages": null, + "methods": { + "password": { + "method": "password", + "config": { + "action": "http://127.0.0.1:4433/self-service/login/methods/password?flow=7c4260f5-55ad-470a-b1d4-e72dd011e4fe", + "method": "POST", + "fields": [ + { + "name": "identifier", + "type": "text", + "required": true, + "value": "" }, { - name: 'identifier', - type: 'text', - required: true + "name": "password", + "type": "password", + "required": true }, { - name: 'password', - type: 'password', - required: true + "name": "csrf_token", + "type": "hidden", + "required": true, + "value": "IwmnH/orHuk6WW5vQ775zcPj01vRFva/rgdIQG4A7G5U74dYcd3kRyCI/Fzf+331gtyEOm2uoTdXKazoAnnyVA==" } ] } } - } + }, + "forced": false } ``` If the login form is filled out incorrectly, errors are included in the response: -```json5 title="$ curl http:///self-service/browser/flows/requests/login?request=0cfb0f7e-3866-453c-9c23-28cc2cb7fead" +```shell script +$ curl -H "Accept: application/json" -s \ + 'http://127.0.0.1:4434/self-service/login/flows?id=7c4260f5-55ad-470a-b1d4-e72dd011e4fe' | jq + { - id: '0cfb0f7e-3866-453c-9c23-28cc2cb7fead', - expires_at: '2020-01-27T16:48:53.8826084Z', - issued_at: '2020-01-27T16:38:53.8826392Z', - request_url: 'http://127.0.0.1:4455/auth/browser/login', - active: 'password', // <-- this is now set - methods: { - password: { - method: 'password', - config: { - messages: [ - { - /* id, type */ - text: 'Please check the data you provided.' + "id": "7c4260f5-55ad-470a-b1d4-e72dd011e4fe", + "type": "browser", + "expires_at": "2020-08-24T12:01:46.0750749Z", + "issued_at": "2020-08-24T11:51:46.0750749Z", + "request_url": "http://127.0.0.1:4433/self-service/login/browser", + "active": "password", + "messages": null, + "methods": { + "password": { + "method": "password", + "config": { + "action": "http://127.0.0.1:4433/self-service/login/methods/password?flow=7c4260f5-55ad-470a-b1d4-e72dd011e4fe", + "method": "POST", + "fields": [ + { + "name": "identifier", + "type": "text", + "required": true, + "value": "i-do-not-exist@exist.org" + }, + { + "name": "password", + "type": "password", + "required": true + }, + { + "name": "csrf_token", + "type": "hidden", + "required": true, + "value": "t41eXWhSYPsObQv8gqk8KHPkUt5ItrQj9vMRYLVWACfAa34a46SaVRS8mc8e7LgQMtsFv/QO46sP3fXI2S8eHQ==" } ], - action: 'http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/login/strategies/password?request=0cfb0f7e-3866-453c-9c23-28cc2cb7fead', - method: 'POST', - fields: [ - /* ... */ - { - name: 'password', - type: 'password', - required: true, - messages: [ + "messages": [ + { + "id": 4000006, + "text": "The provided credentials are invalid, check for spelling mistakes in your password or username, email address, or phone number.", + "type": "error", + "context": {} + } + ] + } + } + }, + "forced": false +} +``` + +If the form is valid (the user exists and the password is correct), ORY Kratos will respond with a HTTP 302 redirect +to the configured redirect URL and set a session cookie using the `Set-Cookie` HTTP Header. + +## API Clients + +API clients are applications which do not run in a browser or in a URL (e.g. `https://my-example.org`). These +are typically native apps (e.g. iOS, Android). + +### Registration + +The registration is initiated by making a request to the Registration API Flow Initialization endpoint: + +```shell script +$ curl -H "Accept: application/json" -s \ + 'http://127.0.0.1:4433/self-service/registration/api' | jq + +{ + "id": "56b5018a-6c47-4675-8ce8-2f463ac77fcb", + "type": "api", + "expires_at": "2020-08-24T12:11:40.5897696Z", + "issued_at": "2020-08-24T12:01:40.5897865Z", + "request_url": "http://127.0.0.1:4433/self-service/registration/api", + "messages": null, + "methods": { + "password": { + "method": "password", + "config": { + "action": "http://127.0.0.1:4433/self-service/registration/methods/password?flow=56b5018a-6c47-4675-8ce8-2f463ac77fcb", + "method": "POST", + "fields": [ + { + "name": "csrf_token", + "type": "hidden", + "required": true, + "value": "4wcp5isuhvN5Ulwqe0p1bZapLP9iP5JYkcrVzmPs6pQzhVtEC66LOXKYppMqXr2PfZDYcPaU17neuXHpQwWatQ==" + }, + { + "name": "password", + "type": "password", + "required": true + }, + { + "name": "traits.email", + "type": "email" + }, + { + "name": "traits.name.first", + "type": "text" + }, + { + "name": "traits.name.last", + "type": "text" + } + ] + } + } + } +} +``` + +As you can see, the flow content is very similar to the one for browser flows. The only difference is that +`type` is now `api` and not `browser`. + +This payload then renders that payload as a native form. The implementation depends on the language, platform, and +framework you use. + +To submit the form, the client makes a request to the action URL which results in an error here because the +password does not match the password policy: + +```shell script +# e.g. http://127.0.0.1:4433/self-service/registration/methods/password?flow=d6e0dd24-f5c6-4926-a526-616a9afeed9d +actionUrl=$(echo $(curl -s -X GET -H "Accept: application/json" "http://127.0.0.1:4433/self-service/registration/api") \ + | jq -r '.methods.password.config.action') + +curl -s -X POST -H "Accept: application/json" -H "Content-Type: application/json" \ + -d '{"traits.email": "api@user.org", "password": "123456", "traits.name.first": "API", "traits.name.last": "User"}' \ + "$actionUrl" | jq +{ + "id": "f7e4dfeb-20b1-466a-8cdc-8575070e3c55", + "type": "api", + "expires_at": "2020-08-24T13:44:25.3643328Z", + "issued_at": "2020-08-24T13:34:25.3643495Z", + "request_url": "http://127.0.0.1:4433/self-service/registration/api", + "active": "password", + "messages": null, + "methods": { + "password": { + "method": "password", + "config": { + "action": "http://127.0.0.1:4433/self-service/registration/methods/password?flow=f7e4dfeb-20b1-466a-8cdc-8575070e3c55", + "method": "POST", + "fields": [ + { + "name": "csrf_token", + "type": "hidden", + "required": true, + "value": "pUShn2vN9kHtQYIYQylabkbCUcRkHAR6R3jeQyzpaAd1RTIFu6NlyTVDQRmMDc6RW+RMq/sCuV4XU1zzKmiqRA==" + }, + { + "name": "password", + "type": "password", + "required": true, + "messages": [ { - /* id, type */ - text: 'password: password is required' + "id": 4000005, + "text": "The password can not be used because the password has been found in at least 23597311 data breaches and must no longer be used..", + "type": "error", + "context": { + "reason": "the password has been found in at least 23597311 data breaches and must no longer be used." + } } ] + }, + { + "name": "traits.email", + "type": "text", + "value": "api@user.org" + }, + { + "name": "traits.name.first", + "type": "text", + "value": "API" + }, + { + "name": "traits.name.last", + "type": "text", + "value": "User" } ] } @@ -271,12 +422,284 @@ response: } ``` -The flow completes otherwise. +A successful registration payload results in: -## API Clients +```shell script +actionUrl=$(echo $(curl -s -X GET -H "Accept: application/json" "http://127.0.0.1:4433/self-service/registration/api") \ + | jq -r '.methods.password.config.action') + +curl -s -X POST -H "Accept: application/json" -H "Content-Type: application/json" \ + -d '{"traits.email": "api@user.org", "password": "iohuasf0897zAJHf", "traits.name.first": "API", "traits.name.last": "User"}' \ + "$actionUrl" | jq +{ + "identity": { + "id": "bf32596a-f853-47c4-91e6-a3f41cf4949d", + "schema_id": "default", + "schema_url": "http://127.0.0.1:4433/schemas/default", + "traits": { + "email": "api@user.org", + "name": { + "last": "User", + "first": "API" + } + }, + "verifiable_addresses": [ + { + "id": "f877db6c-7dfb-45e3-bbeb-ac8349348128", + "value": "api@user.org", + "verified": false, + "via": "email", + "verified_at": null, + "expires_at": "2020-08-24T14:35:59.125873Z" + } + ], + "recovery_addresses": [ + { + "id": "065a908c-82be-4110-bf67-9910f36242b7", + "value": "api@user.org", + "via": "email" + } + ] + } +} +``` + +If the `session` hook is configured, you will also receive a session and a session token: + +```json +{ + "identity": { + "id": "bf32596a-f853-47c4-91e6-a3f41cf4949d", + "schema_id": "default", + "schema_url": "http://127.0.0.1:4433/schemas/default", + "traits": { + "email": "api@user.org", + "name": { + "last": "User", + "first": "API" + } + }, + "verifiable_addresses": [ + { + "id": "f877db6c-7dfb-45e3-bbeb-ac8349348128", + "value": "api@user.org", + "verified": false, + "via": "email", + "verified_at": null, + "expires_at": "2020-08-24T14:35:59.125873Z" + } + ], + "recovery_addresses": [ + { + "id": "065a908c-82be-4110-bf67-9910f36242b7", + "value": "api@user.org", + "via": "email" + } + ] + }, + "session_token": "l98BYBlbE9GYeERnysrSY88YSaZSypQP", + "session": { + "id": "2d754296-ed43-49d0-838d-f268800f109c", + "active": true, + "expires_at": "2020-08-25T13:35:59.1250534Z", + "authenticated_at": "2020-08-24T13:35:59.1336419Z", + "issued_at": "2020-08-24T13:35:59.1250772Z", + "identity": { + "id": "bf32596a-f853-47c4-91e6-a3f41cf4949d", + "schema_id": "default", + "schema_url": "http://127.0.0.1:4433/schemas/default", + "traits": { + "email": "api@user.org", + "name": { + "last": "User", + "first": "API" + } + }, + "verifiable_addresses": [ + { + "id": "f877db6c-7dfb-45e3-bbeb-ac8349348128", + "value": "api@user.org", + "verified": false, + "via": "email", + "verified_at": null, + "expires_at": "2020-08-24T14:35:59.125873Z" + } + ], + "recovery_addresses": [ + { + "id": "065a908c-82be-4110-bf67-9910f36242b7", + "value": "api@user.org", + "via": "email" + } + ] + } + } +} +``` + +### Login + +The login is initiated by making a request to the Login API Flow Initialization endpoint: + +```shell script +$ curl -H "Accept: application/json" -s \ + 'http://127.0.0.1:4433/self-service/login/api' | jq + +{ + "id": "2cf23130-9164-44be-8fe8-badade883f24", + "type": "api", + "expires_at": "2020-08-24T12:04:15.2332125Z", + "issued_at": "2020-08-24T11:54:15.2332125Z", + "request_url": "http://127.0.0.1:4433/self-service/login/api", + "messages": null, + "methods": { + "password": { + "method": "password", + "config": { + "action": "http://127.0.0.1:4433/self-service/login/methods/password?flow=2cf23130-9164-44be-8fe8-badade883f24", + "method": "POST", + "fields": [ + { + "name": "identifier", + "type": "text", + "required": true, + "value": "" + }, + { + "name": "password", + "type": "password", + "required": true + }, + { + "name": "csrf_token", + "type": "hidden", + "required": true, + "value": "OoFnnSXYUjRqbuPvRay7pafxkLPExzmL68Eg15EMxdQbTRyL1s6FUlLgz8VzcAcUVjd7x7EJgjA6koUAn+4kSg==" + } + ] + } + } + }, + "forced": false +} +``` + +As you can see, the flow content is very similar to the one for browser flows. The only difference is that +`type` is now `api` and not `browser`. + +This payload then renders that payload as a native form. The implementation depends on the language, platform, and +framework you use. + +To submit the form, the client makes a request to the action URL which results in an error here because the +user is unknown: + +```shell script +# e.g. http://127.0.0.1:4433/self-service/login/methods/password?flow=d6e0dd24-f5c6-4926-a526-616a9afeed9d +actionUrl=$(echo $(curl -s -X GET -H "Accept: application/json" "http://127.0.0.1:4433/self-service/login/api") \ + | jq -r '.methods.password.config.action') + +curl -s -X POST -H "Accept: application/json" -H "Content-Type: application/json" \ + -d '{"identifier": "i-do-not-exist@user.org", "password": "the-wrong-password"}' \ + "$actionUrl" | jq + +{ + "id": "37703ca6-bf0a-497d-aead-ce573ab67eb6", + "type": "api", + "expires_at": "2020-08-24T12:08:18.4591524Z", + "issued_at": "2020-08-24T11:58:18.4591524Z", + "request_url": "http://127.0.0.1:4433/self-service/login/api", + "active": "password", + "messages": null, + "methods": { + "password": { + "method": "password", + "config": { + "action": "http://127.0.0.1:4433/self-service/login/methods/password?flow=37703ca6-bf0a-497d-aead-ce573ab67eb6", + "method": "POST", + "fields": [ + { + "name": "identifier", + "type": "text", + "required": true, + "value": "i-do-not-exist@user.org" + }, + { + "name": "password", + "type": "password", + "required": true + }, + { + "name": "csrf_token", + "type": "hidden", + "required": true + } + ], + "messages": [ + { + "id": 4000006, + "text": "The provided credentials are invalid, check for spelling mistakes in your password or username, email address, or phone number.", + "type": "error", + "context": {} + } + ] + } + } + }, + "forced": false +} +``` + +If user and password are correct, the server responds with a session token and a session: + +```shell script +# e.g. http://127.0.0.1:4433/self-service/login/methods/password?flow=d6e0dd24-f5c6-4926-a526-616a9afeed9d +actionUrl=$(echo $(curl -s -X GET -H "Accept: application/json" "http://127.0.0.1:4433/self-service/login/api") \ + | jq -r '.methods.password.config.action') -API-based login and registration using this strategy will be addressed in a -future release of ORY Kratos. +curl -s -X POST -H "Accept: application/json" -H "Content-Type: application/json" \ + -d '{"identifier": "api@user.org", "password": "iohuasf0897zAJHf"}' \ + "$actionUrl" | jq + +{ + "session_token": "oFZzgLpsacUpUy2cvQPtrGa2046WcXCR", + "session": { + "id": "8f660ce3-69ec-4aeb-9fda-f9230dc3243f", + "active": true, + "expires_at": "2020-08-25T13:42:15.7411522Z", + "authenticated_at": "2020-08-24T13:42:15.7411522Z", + "issued_at": "2020-08-24T13:42:15.7412042Z", + "identity": { + "id": "bf32596a-f853-47c4-91e6-a3f41cf4949d", + "schema_id": "default", + "schema_url": "http://127.0.0.1:4433/schemas/default", + "traits": { + "email": "api@user.org", + "name": { + "last": "User", + "first": "API" + } + }, + "verifiable_addresses": [ + { + "id": "f877db6c-7dfb-45e3-bbeb-ac8349348128", + "value": "api@user.org", + "verified": false, + "via": "email", + "verified_at": null, + "expires_at": "2020-08-24T14:35:59.125873Z" + } + ], + "recovery_addresses": [ + { + "id": "065a908c-82be-4110-bf67-9910f36242b7", + "value": "api@user.org", + "via": "email" + } + ] + } + } +} +``` ## Security and Defenses diff --git a/docs/src/theme/SelfServiceAPIFlow.js b/docs/src/theme/SelfServiceAPIFlow.js index ee4d28495f3..9481581b0b1 100644 --- a/docs/src/theme/SelfServiceAPIFlow.js +++ b/docs/src/theme/SelfServiceAPIFlow.js @@ -3,6 +3,8 @@ import Mermaid from "./Mermaid"; const chart = ({ flows = ['login', 'registration', 'settings', '...'], + methods = ['password', 'oidc', '...'], + interactions = ['"Log in"', '"Sign Up"', '"Update Email"', '...'], success = 'Perform flow-specific action (e.g. create user, set session cookie, ...)' }) => ` sequenceDiagram @@ -13,11 +15,11 @@ sequenceDiagram K-->>K: Create and store new ${flows.join(', ')} flow K->>B: HTTP 200 OK with flow as application/json payload B-->>B: Render form using e.g. Native iOS UI Elements - B-->>B: User fills out forms, clicks e.g. "Log in", "Sign Up", "Update Email", ... - B->>K: REST POST to e.g. /self-service/login/methods/password + B-->>B: User fills out forms, clicks e.g. ${interactions} + B->>K: REST POST to e.g. /self-service/<${flows.join('|')}>/methods/<${methods.join('|')}> K-->>K: Validates and processes payload alt Form payload is valid valid - K->>B: Perform flow-specific action (e.g. create user, return session cookie, ...) + K->>B: ${success} else Login data invalid K-->>K: Update and store flow (e.g. add form validation errors) K->>B: Respond with e.g. HTTP 400 Bad Request and updated flow as payload