Deployment to Ory Network

getlarge

Edouard Maleix

Posted on May 22, 2024

Deployment to Ory Network

In this final installment of our series, we transition from a development environment to a production-ready setup by deploying our NestJS application integrated with self-hosted Ory services to the Ory Network. This process showcases the interoperability of Ory Network with our existing self-hosted setup and highlights how Ory Network can simplify deployment processes and reduce operational overhead.

Deployment Process Overview

Deploying to Ory Network involves several critical steps to ensure your deployment process is secure and scalable. Here’s a high-level overview of the process, depicted as a flowchart:

Deployment Process Overview

Detailed Steps for Deployment

Account Registration

We start by registering an account with Ory Network. This account will manage our projects and workspaces within the Ory Network and add collaborators to our projects.

Create Workspace

Create a new workspace within the Ory Network. This workspace will help you organize your projects effectively and is the first step towards deploying your application. Ory Network allows you to create multiple workspaces, each serving as an isolated environment for different projects and billing.

Create Workspace

Create project

Within your new workspace, register a new project named CatFostering. In practice, ensure your project is scoped appropriately for the environment (development, staging, production).

Create Project

Install Ory CLI

Install the Ory CLI to interact with the Ory Network from your command line, which is essential for subsequent configuration steps.
When you have the CLI installed, you can run the following command and follow the prompts to authenticate:

ory auth
Enter fullscreen mode Exit fullscreen mode

Build tools to configure the Ory Network project

Before updating the Ory Network project, we need to generate the necessary configuration file from our existing configuration templates for Kratos and Keto and translate them into a format compatible with Ory Network. There are a few things to note:

  • File references pointing to local files (file:// protocol) won't work on Ory Network. The file content should be inlined after being encoded to base64 or uploaded to cloud storage and linked to the configuration.
  • URLs pointing to local services (127.0.0.1, localhost, 192.168.x.y) will result in a schema validation error. They must resolve to a public address.
  • The following configuration keys are ignored by Ory Network:
    • dsn
    • log
    • server
    • secrets
    • session.cookie.domain
    • session.cookie.name
    • session.cookie.path
    • cookies.path
    • cookies.domain

The Ory Network configuration file should be in JSON or YAML format and follow the schema defined by Ory Network:

type OryNetworkConfig = {
  name: string;
  services: {
    identity: {
      config: Record<string, unknown>;
    };
    permission: {
      config: Record<string, unknown>;
    };
  };
};
Enter fullscreen mode Exit fullscreen mode

Note:
Under identity and permission, the config object should contain the configuration for Kratos and Keto, respectively.

We will create a function - generateOryNetworkConfig - relying on the previously built tools to generate the configuration file for Ory Network.

// from https://github.com/getlarge/cat-fostering/blob/main/tools/ory/helpers.ts
// ...
export function generateOryNetworkConfig(envFile: string) {
  const kratosConfigFilePath = join(
    ORY_NETWORK_DIRECTORY,
    'identity.yaml'
  ) as ConfigFilepath;
  const ketoConfigFilePath = join(
    ORY_NETWORK_DIRECTORY,
    'permission.yaml'
  ) as ConfigFilepath;

  generateOryKetoConfig(envFile, ketoConfigFilePath);
  generateOryKratosConfig(envFile, kratosConfigFilePath);
  const oryConfig: OryNetworkConfig = {
    name: 'CatFostering',
    services: {
      identity: {
        config: getOryConfig(kratosConfigFilePath),
      },
      permission: {
        config: getOryConfig(ketoConfigFilePath),
      },
    },
  };
  return oryConfig;
}
Enter fullscreen mode Exit fullscreen mode

Expose the application to the internet

Our NestJS application receives webhooks from Ory Hydra, which is running locally. With Ory Network running on the cloud, the application must be accessible via a public URL. To expose your local development environment to the internet, utilize a tunnel service such as Tailscale Funnel, ngrok, webhook.site, or others. This step is crucial for receiving webhooks from Ory Network.

I am a fan of Tailscale products due to their simplicity and ease of use, and Funnel makes no exception. To expose your local application, you would run the following command:

tailscale funnel --set-path cat-fostering 3000
Enter fullscreen mode Exit fullscreen mode

Note:
This command exposes your local application running on port 3000 to the internet under the FQDN generated by Tailscale Magic DNS with the path /cat-fostering (e.g., https://<machine_name>.<domain_name>.ts.net/cat-fostering).

Update Ory Network Project

To generate the Ory configuration, we first need to store the variables to substitute in the .env.development file using the values from:

  • The Ory Network console under the Project Settings (e.g. https://console.ory.sh/projects/<project-id>/settings)
  • The tunnel service for the public URL
  • The encoded files to inline in the configuration

The following variables are required:

# env.development
kratos_cookie_domain="<ory_project_domain>"
kratos_identity_schemas_default="base64://<base64_encoded_content>"
kratos_selfservice_allowed_return_urls="https://<ory_project_domain>, https://<ory_project_domain>/ui/error, https://<ory_project_domain>/ui/login, https://<ory_project_domain>/ui/recovery, https://<ory_project_domain>/ui/settings, https://<ory_project_domain>/ui/registration, https://<ory_project_domain>/ui/verification, /ui/logout, /ui/consent, /ui/welcome, /ui/sessions"
kratos_selfservice_default_browser_return_url="/ui/welcome"
kratos_selfservice_flows_errors_ui_url="https://<ory_project_domain>/ui/error"
kratos_selfservice_flows_login_after_hook_config_url="<tunnel_url>/api/users/on-sign-in"
kratos_selfservice_flows_login_after_hook_config_auth_config_value="<your-own-internal-api-key>"
kratos_selfservice_flows_login_after_hook_config_body="base64://<base64_encoded_content>"
kratos_selfservice_flows_login_after_hook_config_can_interrupt="true"
kratos_selfservice_flows_login_after_hook_config_response_ignore="false"
kratos_selfservice_flows_login_after_hook_config_response_parse="false"
kratos_selfservice_flows_login_ui_url="https://<ory_project_domain>/ui/login"
kratos_selfservice_flows_recovery_ui_url="https://<ory_project_domain>/ui/recovery"
kratos_selfservice_flows_registration_after_hook_config_url="<tunnel_url>/api/users/on-sign-up"
kratos_selfservice_flows_registration_after_hook_config_auth_config_value="<your-own-internal-api-key>"
kratos_selfservice_flows_registration_after_hook_config_body="base64://<base64_encoded_content>"
kratos_selfservice_flows_registration_after_hook_config_can_interrupt="true"
kratos_selfservice_flows_registration_after_hook_config_response_ignore="false"
kratos_selfservice_flows_registration_after_hook_config_response_parse="true"
kratos_selfservice_flows_registration_ui_url="https://<ory_project_domain>/ui/registration"
kratos_selfservice_flows_settings_ui_url="https://<ory_project_domain>/ui/settings"
kratos_selfservice_flows_verification_ui_url="https://<ory_project_domain>/ui/verification"
kratos_selfservice_methods_passkey_config_rp_id="<ory_project_domain>"
kratos_selfservice_methods_passkey_config_rp_origins="https://<ory_project_domain>"
kratos_selfservice_methods_passkey_enabled="false"
kratos_selfservice_methods_webauthn_config_rp_id="<ory_project_domain>"
kratos_selfservice_methods_webauthn_config_rp_origins="https://<ory_project_domain>"
kratos_selfservice_methods_webauthn_enabled="false"
kratos_secrets_cookie="cookie_secret_not_good_not_secure"
kratos_secrets_cipher="32-LONG-SECRET-NOT-SECURE-AT-ALL"
kratos_serve_admin_base_url="https://<ory_project_domain>/"
kratos_serve_public_base_url="https://<ory_project_domain>/"
kratos_serve_public_cors_enabled="false"
kratos_session_cookie_domain="<ory_project_domain>"
kratos_session_cookie_name="ory_session_<ory_project_slug>"
keto_namespaces_location="base64://<base64_encoded_content>"
Enter fullscreen mode Exit fullscreen mode

Tip:
To encode a file to base64, you can use the following command:

base64 -i <file_path>

And copy the output to the configuration file, prepending base64://. (e.g. base64://<base64_encoded_content>)

The Ory CLI contains a command to update the project configuration with the generated configuration file. To simplify its usage, we will create a helper function - updateOryNetworkConfig - that generates the configuration file and updates the project configuration.

// from https://github.com/getlarge/cat-fostering/blob/main/tools/ory/helpers.ts
// ...
export function updateOryNetworkConfig(projectId: string, envFile: string) {
  const oryConfig = generateOryNetworkConfig(envFile);
  console.log(inspect(oryConfig, { depth: null }));
  const oryNetworkConfigPath = join(ORY_NETWORK_DIRECTORY, 'config.json');
  writeFileSync(oryNetworkConfigPath, JSON.stringify(oryConfig));
  execSync(
    `ory update project ${projectId} --format json --file ${oryNetworkConfigPath}`,
    {
      stdio: 'inherit',
    }
  );
}
Enter fullscreen mode Exit fullscreen mode

Note:
The updateOryNetworkConfig function will be imported and used in generate-config.ts, which will be referenced in the package.json scripts.

Run the following command to generate the Ory configuration and update the project configuration on the Ory Network:

npm run ory:update:network -e .env.development --project-id <project_id>
Enter fullscreen mode Exit fullscreen mode

The CLI output should indicate the successful update; we can check the Ory Console for the updated configuration.

Ory Console Permissions

Note:
Above is an example of the permissions configuration in the Ory Console. The configuration should reflect the settings in the encoded value of infra/ory-keto/namespaces.ts.

This tool can also be a great addition to a CI/CD pipeline by automating the configuration update process for multiple Ory Network tenants.
Here is a simplified example using GitHub Actions to trigger the deployment process:

name: Update Ory Network Config

on:
  push:
    branches:
      - main

env:
  ORY_PROJECT_ID: ${{ vars.ORY_PROJECT_ID }}
  ORY_PROJECT_DOMAIN: ${{ vars.ORY_PROJECT_DOMAIN }}
  ORY_PROJECT_URL: https://${{ vars.ORY_PROJECT_DOMAIN }}
  APPLICATION_URL: ${{ vars.APPLICATION_URL }}

jobs:
  run-ory-cli:
    runs-on: ubuntu-latest
    steps:
      # checkout the repository, setup node, ...
      - name: encode Files to Base64
        id: ory-files
        shell: bash

        run: |
          echo "KRATOS_IDENTITY_SCHEMAS_DEFAULT=$(base64 -i infra/ory-kratos/identity-schemas.json)" >> $GITHUB_OUTPUT
          echo "KRATOS_SELF_SERVICE_FLOWS_LOGIN_AFTER_HOOK_CONFIG_BODY=$(base64 -i infra/ory-kratos/after-webhook.jsonnet)" >> $GITHUB_OUTPUT
          echo "KETO_NAMESPACES_LOCATION=$(base64 -i infra/ory-kratos/identity-schemas.json)" >> $GITHUB_OUTPUT

      - name: generate Ory Network Config
        env:
          kratos_identity_schemas_default: base64://${{ steps.ory-files.outputs.KRATOS_IDENTITY_SCHEMAS_DEFAULT }}
          kratos_selfservice_allowed_return_urls: ${{ env.ORY_PROJECT_URL }}, ${{ env.ORY_PROJECT_URL }}/ui/error, ${{ env.ORY_PROJECT_URL }}/ui/login, ${{ env.ORY_PROJECT_URL }}/ui/recovery, ${{ env.ORY_PROJECT_URL }}/ui/settings, ${{ env.ORY_PROJECT_URL }}/ui/registration, ${{ env.ORY_PROJECT_URL }}/ui/verification, /ui/logout, /ui/consent, /ui/welcome, /ui/sessions
          kratos_selfservice_default_browser_return_url: /ui/welcome
          kratos_selfservice_flows_errors_ui_url: ${{ env.ORY_PROJECT_URL }}/ui/error
          kratos_selfservice_flows_login_after_hook_config_url: ${{ env.APPLICATION_URL }}/api/users/on-sign-in
          kratos_selfservice_flows_login_after_hook_config_auth_config_value: ${{ secrets.ORY_ACTION_API_KEY }}
          kratos_selfservice_flows_login_after_hook_config_body: base64://${{ steps.ory-files.outputs.KRATOS_SELF_SERVICE_FLOWS_LOGIN_AFTER_HOOK_CONFIG_BODY }}
          kratos_selfservice_flows_login_after_hook_config_can_interrupt: true
          kratos_selfservice_flows_login_after_hook_config_response_ignore: false
          kratos_selfservice_flows_login_after_hook_config_response_parse: false
          kratos_selfservice_flows_login_ui_url: ${{ env.ORY_PROJECT_URL }}/ui/login
          kratos_selfservice_flows_recovery_ui_url: ${{ env.ORY_PROJECT_URL }}/ui/recovery
          kratos_selfservice_flows_registration_after_hook_config_url: ${{ env.APPLICATION_URL }}/api/users/on-sign-up
          kratos_selfservice_flows_registration_after_hook_config_auth_config_value: ${{ secrets.ORY_ACTION_API_KEY }}
          kratos_selfservice_flows_registration_after_hook_config_can_interrupt: true
          kratos_selfservice_flows_registration_after_hook_config_response_ignore: false
          kratos_selfservice_flows_registration_after_hook_config_response_parse: true
          kratos_selfservice_flows_registration_ui_url: ${{ env.ORY_PROJECT_URL }}/ui/registration
          kratos_selfservice_flows_settings_ui_url: ${{ env.ORY_PROJECT_URL }}/ui/settings
          kratos_selfservice_flows_verification_ui_url: ${{ env.ORY_PROJECT_URL }}/ui/verification
          kratos_selfservice_methods_passkey_config_rp_id: ${{ env.ORY_PROJECT_DOMAIN }}
          kratos_selfservice_methods_passkey_config_rp_origins: ${{ env.ORY_PROJECT_URL }}
          kratos_selfservice_methods_passkey_enabled: false
          kratos_selfservice_methods_webauthn_config_rp_id: ${{ env.ORY_PROJECT_DOMAIN }}}
          kratos_selfservice_methods_webauthn_config_rp_origins: ${{ env.ORY_PROJECT_URL }}
          kratos_selfservice_methods_webauthn_enabled: false
          kratos_serve_admin_base_url: ${{ env.ORY_PROJECT_URL }}/
          kratos_serve_public_base_url: ${{ env.ORY_PROJECT_URL }}/
          kratos_serve_public_cors_enabled: false
          keto_namespaces_location: base64://${{ steps.ory-files.outputs.KRATOS_SELF_SERVICE_FLOWS_LOGIN_AFTER_HOOK_CONFIG_BODY }}
        run: npm run ory:generate:network

      - name: Update Ory Network Config
        uses: lomsa-dev/ory-cli-action@v1.0.8 # use the latest version
        with:
          username: ${{ secrets.ORY_USERNAME }}
          password: ${{ secrets.ORY_PASSWORD }}
          commands: |
            update project {{ env.ORY_PROJECT_ID }} --file infra/ory-network/config.json
Enter fullscreen mode Exit fullscreen mode

Create API key

Generate an API key within the Ory Network console under the developers(https://console.ory.sh/projects/<project-id>/developers) tab. This key will authenticate your application's requests to Ory services.

Create Ory API Key

Configure CatFostering Application

Update your application's configuration to use the API keys and endpoints provided by Ory Network, ensuring all interactions are routed correctly.

# apps/cat-fostering/.env
POSTGRES_URL="postgresql://dbuser:secret@localhost:5432/appdb"
ORY_ACTION_API_KEY="<your-own-internal-api-key>"
ORY_KETO_ADMIN_URL="https://<Ory_project_slug>.projects.oryapis.com"
ORY_KETO_PUBLIC_URL="https://<Ory_project_slug>.projects.oryapis.com"
ORY_KRATOS_ADMIN_URL="https://<Ory_project_slug>.projects.oryapis.com"
ORY_KRATOS_PUBLIC_URL="https://<Ory_project_slug>.projects.oryapis.com"
ORY_KRATOS_API_KEY="ory_pat_xxxxxxxx"
ORY_KETO_API_KEY="ory_pat_xxxxxxxx"
Enter fullscreen mode Exit fullscreen mode

You can now start your application and test the integration with Ory Network.

Ory Proxy and Ory Tunnel

We won't need them for our case since the CatFostering doesn't have a front-end web application. However, it is worth knowing that Ory Proxy and Ory Tunnel are tools included in the Ory CLI to assist with local development. They are used to expose Ory APIs under the same top-level domain as your application to avoid CORS issues.

Conclusion

In this article, we explored the deployment process to Ory Network to transition our NestJS application from a development environment to a production-ready setup. Following the detailed steps outlined above, you can move your local Ory setup to the cloud in no time. The interoperability of Ory Network with our existing self-hosted setup and its ease of deployment make it a compelling choice for developers looking to streamline their deployment processes and reduce operational overhead.

To go further, you can explore additional features of Ory Network, such as CORS configuration and custom domains, to enhance your application's security and user experience.

To complete your knowledge on this topic, I suggest reading the following article written by the Ory team: Ory Network or self-hosted?

I hope this series has provided valuable insights to secure your NestJS applications with Ory and streamline your development workflows.

💖 💪 🙅 🚩
getlarge
Edouard Maleix

Posted on May 22, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related