Ory Kratos is a user authentication and management system. React+AntD: React+AntD: AntD: React+AntD

  • Code: github.com/ikuokuo/sta…

Understand Kratos

Get the code

git clone- b v0.7.0 - alpha. 1 -- -- the depth 1 https://github.com/ory/kratos.gitCopy the code

Look at the API

Go – swagger view:

cd kratos
swagger serve -F=swagger ./spec/swagger.json
Copy the code

Run the service

Docker – compose operation:

cd kratos
docker-compose -f quickstart.yml -f quickstart-postgres.yml -f quickstart-standalone.yml up --build --force-recreate
# If you have SELinux, run: -f quickstart-selinux.yml
Copy the code

To run the official Quickstart example, can visit http://127.0.0.1:4455/dashboard experience.

Look at the DB

PgAdmin open (see Quickstart -postgres.yml for DB information) :

See table:

Check the configuration

cd kratos
cat contrib/quickstart/kratos/email-password/kratos.yml
Copy the code

Setting environment variables can override. Expressed as a _ hierarchy, such as SELFSERVICE_FLOWS_SETTINGS_UI_URL = < value > cover selfservice flows. Settings. Ui_url.

The Self – Service process

  • Registration
  • Login
  • Logout
  • User Settings
  • Account Recovery
  • Address Verification
  • User-Facing Error
  • 2FA / MFA

Browser flow

Client flow

Hands-on configuration: Kratos service

  • Ory Kratos
    • Public API (port 4433)
    • Admin API (port 4434)
    • Postgres DB (port 5432)
    • Browser Return URL (port 3000)
  • MailSlurper: a development SMTP server
    • Server UI (port 4436)

The configuration file

  • Ory – kratos/config/kratos. Yml: the configuration file
  • Ory – kratos/config/identity. Schema. Json: certification json mode

Startup file

  • Ory-kratos /start.yml: Docker Compose file

Run the service

cd ory-kratos
docker-compose -f start.yml up --build --force-recreate
Copy the code

If you want to run the official self-service UI example, then:

docker-compose -f start.yml -f start-ui-node.yml up --build --force-recreate
Copy the code

After that, visit http://127.0.0.1:3000/ to experience. When you Register new Account/Reset Password, you can access the virtual SMTP service http://127.0.0.1:4436 to receive emails.

Hands-on implementation: browser flow

React + Ant Design

Creating a React Application

yarn create react-app my-web --template typescript
cd my-web
yarn start
Copy the code

Visit http://localhost:3000/ to see the React welcome page.

The introduction of AntD

yarn add antd
Copy the code

Modify SRC/app.tsx to introduce ANTD component:

import React, { Component } from 'react'
import { Button } from 'antd';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <Button type="primary">Button</Button>
        </header>
      </div>); }}export default App;
Copy the code

Modify SRC/app.css to introduce antD style:

@import '~antd/dist/antd.css';
Copy the code

The ANTD blue button component is visible.

The introduction of Sass

yarn add node-sass
Copy the code

Change the suffix CSS to SCSS and import from TSX.

The introduction of the Router

yarn add react-router-dom @types/react-router-dom
Copy the code

Implement the following UI in the Pages directory:

SRC/Pages Feature Route ├─ Dashboard.tsx home /, / Dashboard ├── Error.tsx Error/Error ├── Login. TSX Login ├─ auth/ Login ├─ recovery.tsx /auth/ Registration ├─ settings.tsx set/Settings ├─ verificationCopy the code

The introduction of the SDK

Yarn add @ ory/[email protected]Copy the code

registered

APIs:

  • GET /self-service/registration/browser: Initializes the registration process
  • GET /self-service/registration/flows: Gets the registration process
  • POST /self-service/registration: Submit the registration process

Processing flow after page loading:

componentDidMount() {
  // Get the flow id parameter
  const flowId = utils.parseUrlQuery("flow".this.props.location) as string;

  // No flow ID, initialize the registration process
  if(! flowId || ! utils.isString(flowId)) {console.log("No flow ID found in URL, initializing registration flow.");
    utils.redirectToSelfService("/self-service/registration/browser");
    return;
  }

  // Obtain the registration process information according to the flow ID
  authPublicApi
    .getSelfServiceRegistrationFlow(flowId, undefined, {
      withCredentials: true,
    })
    .then((res: AxiosResponse<SelfServiceRegistrationFlow>) = > {
      if (utils.assertResponse(res)) {
        utils.redirectToSelfService("/self-service/registration/browser");
        return;
      }
      this.setState({ flowId: flowId, flow: res.data });
    })
    .catch(utils.redirectOnError);
}
Copy the code

The flow information this.state.flow is as follows:

{
  "id": "74c643a1-f302-45c9-a760-1ad7b1157e1c"."type": "browser"."expires_at": "The 2021-07-20 T05:22:30. 958717 z"."issued_at": "The 2021-07-20 T05:12:30. 958717 z"."request_url": "http://127.0.0.1:4433/self-service/registration/browser"."ui": {
    "action": "Http://127.0.0.1:4433/self-service/registration? flow=74c643a1-f302-45c9-a760-1ad7b1157e1c"."method": "POST"."nodes": [{
      "type": "input"."group": "default"."attributes": {
        "name": "csrf_token"."type": "hidden"."value": "QQyUDHa4KJ3M6mowHHN4pboN4iaUOZL+4gYVtKYRWzSdWjSNcW5dG/SNzocyqqqAtV48KzQVMIC6X+Pv3tNPNw=="."required": true."disabled": false
      },
      "messages": []."meta": {}}, {"type": "input"."group": "password"."attributes": {
        "name": "traits.email"."type": "email"."disabled": false
      },
      "messages": []."meta": {
        "label": {
          "id": 1070002,
          "text": "E-Mail"."type": "info"}}}, {... }}}]Copy the code

After that, create a form based on the process information:

<Card title="Register new account" bordered={false} > {/* Process message display */}
  {this.state.flow.ui.messages &&
    this.state.flow.ui.messages.map((m: UiText, index) = > (
      <Alert
        key={index}
        message={m.text}
        type={m.type as AlertProps["type"]}
        style={{ marginBottom: 16 }}
        showIcon
      />
    ))}
  {/* Process form creation */}
  <Form
    name="register"
    ref={this.formRef}
    encType="application/x-www-form-urlencoded"
    action={this.state.flow.ui.action}
    method={this.state.flow.ui.method}
    onFinish={onFinish}
  >
    {this.state.flow.ui.nodes.map((node, index) = > {
      returnReact.cloneElement(ui.toUiNodeAntd(node)! , {key: index,
      });
    })}
  </Form>
</Card>
Copy the code

The form onFinish handles the submission:

const onFinish = (values: any) = > {
  // AntD Form does not submit the original HTML Form
  // - Cannot submit the find form directly, the value has been cleared
  // - Create from commit, and AntD from have no effect on each other
  ui.submitViaForm(this.state.flow! .ui, values);// Alternatively, file with '/self-service/registration/ API'
  // this.submitViaApi(values);
};
Copy the code

The login

  • GET /self-service/login/browser: Initializes the login process
  • GET /self-service/login/flows: Obtains the login process
  • POST /self-service/login: Submit the login process

Same as the registration process.

After login, you can obtain the authorization information through WHOami:

  • GET /sessions/whoami: Obtain authorization information
authPublicApi
  .toSession(undefined.undefined, {
    withCredentials: true,
  })
  .then((res: AxiosResponse<Session>) = > {
    if (utils.assertResponse(res)) {
      utils.redirectToSelfService("/self-service/login/browser");
      return;
    }
    this.setState({ session: res.data });
  })
  .catch((err: AxiosError) = > utils.redirectOnError(err, "/auth/login"));
Copy the code

The Dashboard page shows the authorization information:

validation

  • GET /self-service/verification/browser: Initializes the verification process
  • GET /self-service/verification/flows: Obtain the verification process
  • POST /self-service/verification: Submit the verification process

Same as the registration process.

restore

  • GET /self-service/recovery/browser: Initializes the recovery process
  • GET /self-service/recovery/flows: Obtain the recovery process
  • POST /self-service/recovery: Submit the recovery process

Same as the registration process.

Set up the

  • GET /self-service/settings/browser: Initial configuration process
  • GET /self-service/settings/flows: Obtain the configuration process
  • POST /self-service/settings: The configuration process is complete

Same as the registration process.

However, it should be noted that when creating forms based on process information, please distinguish groups and build multiple forms:

const nodesGroup: Record<
  string, { title? :string; nodes? :Array<UiNode>;
  }
> = {
  default: {},
  profile: { title: "Profile" },
  password: { title: "Password" },
  oidc: { title: "Social Sign In"}};for (const [k, v] of Object.entries(nodesGroup)) {
  nodesGroup[k] = {
    title: v.title,
    nodes: ui.onlyNodes(this.state.flow! .ui.nodes, k), }; }Copy the code
<Card title="Settings" bordered={false} > {this.state.flow.ui.messages &&
    this.state.flow.ui.messages.map((m: UiText, index) = > (
      <Alert
        key={index}
        message={m.text}
        type={m.type as AlertProps["type"]}
        style={{ marginBottom: 16 }}
        showIcon
      />
    ))}
  {/* Split Form by group here. Otherwise, one AntD Form method conflicts. */}
  {Object.entries(nodesGroup)
    .filter(([k, v]) = >k ! = ="default"&& v && v.nodes! .length >0)
    .map(([k, v], index) = > (
      <Form
        key={index}
        name={k}
        encType="application/x-www-form-urlencoded"
        action={this.state.flow! .ui.action}
        method={this.state.flow! .ui.method}
        onFinish={onFinish}
      >
        <Form.Item>
          <div>{v.title}</div>
        </Form.Item>{v .nodes! .concat(nodesGroup["default"].nodes!) .map((node, index) => { return React.cloneElement(ui.toUiNodeAntd(node)! , { key: index, }); })}</Form>
    ))}
</Card>
Copy the code

logout

  • GET /self-service/logout/browser: Creates the logout URL
  • POST /self-service/logout: The logout process is complete

Create a logout URL after the page loads,

authPublicApi
  .createSelfServiceLogoutFlowUrlForBrowsers(undefined, {
    withCredentials: true,
  })
  .then((res: AxiosResponse<SelfServiceLogoutUrl>) = > {
    this.setState({ logoutUrl: res.data.logout_url });
  })
  .catch((err) = > {
    // console.log(err);
  });
Copy the code

After that, the page adds a logout button:

{this.state.logoutUrl && (
  <Button
    type="link"
    shape="circle"
    href={this.state.logoutUrl}
    icon={<LogoutOutlined />} / >
)}
Copy the code

reference

  • ORY Kratos ExpressJS Self-Service UI Reference
  • Kratos React Example

GoCoding personal practice experience sharing, please pay attention to the public account!