Skip to content

Ory Keto: Authorization and Access Control as a Service

Internet has come a long way since its inception. The first few years might have been a new adventure for those building web applications, but in the modern day software development and in 2024, you rarely stop to question most of the common practices around the industry.

One of the most frequent requirement for any application is to have some sort of access control policy. The most used approach in today's world is the use of RBAC. It makes a lot of sense to treat a group of one or multiple identities of a system the same way and grant or deny them a specific set of permissions.

Ory Keto comes with all the batteries included. It provides a fearless authorization platform, friendly API for developers, and scalable stateless application.

If you're creating an application over HTTP these days, chances are, Ory Keto has a lot to offer you. Stick around till the end to find out how.

Introduction

Today's software development is rarely just the software itself. We all get tangled up on all the other aspects of production-readiness and the ever so famous checklist.

We find ourselves doing application development only 20% of the time. The rest gets us all so busy with the never ending yak-shavings1.

Fortunately for us, Ory comes with a bundle of plug-and-play products to make our lives easier. We will have one less aspect to worry about when it comes to securing our application in the wild world out there.

With Ory Keto, you can grant or deny access to your application in a flexible manner, customize the permission sets as required, and grow effortlessly as your application scales.

Subscribe to the Newsletter

Receive the latest blog post updates in your mailbox.

    No Spam. Unsubscribe at any time.

    What is Ory Keto?

    Ory Keto is a one-stop shop for all the authorization needs. With Keto, you can define policies, roles, and permissions for your application. These policies can be written either using a programming language SDK in the Ory Permission Language(OPL)2, or a configuration file in JSON or YAML3.

    It has a friendly REST API4 that you can use to query or modify the policies on the fly.

    In short, with Ory Keto you can answer the second most important question:

    Is the user/identity X allowed to access the resource Y?

    In that the first one is: Is the request authenticated/logged-in?

    We will get to the nitty gritty details of how it identifies how to answer this under the hood in a bit, but the important thing to mention here is that it simplifies the authorization problem by centralizing the policies and consolidating the definitions in one place.

    Why Ory Keto?

    Keto is not the only authorization solution out there. The reality is that there are countless other alternatives, each with their own strength and flexibility. You may end up getting lost finding the right fit for your setup!

    There are programming language authorization libraries such as Casbin5 and OPA6. There are cloud-based solutions such as Auth07 and Okta8.

    In my experience managing production workloads over the years, I have found that the authorization is mostly an operational concern.

    Although some folks may disagree, I have found that taking the authorization out of the application simplifies the maintainability and long-term success of the application, allowing the developers focusing on increasing the success and richness of the business logic.

    However, when placing RBAC and other authorization mechanisms inside the application, you'll end up with a lot of code that is mostly relevant to the production environment and only slows the developers down when working locally.

    Why is that an issue? one might ask. Well, imagine having to populate your access policy documents at the start of the application on each local development. That's a waste of computation and engineering time.

    On top of that, every time a new member joins the team, you end up having to explain the authorization mechanism to them and how to set the whole thing up.

    We may get clever automating the process by creating a migration step for the authorization policies. However, that only pushes the problem to a different layer rather than solving it.

    All in all, Ory Keto is a great place to offload such a tedious task, and it comes with a lot of flexibility in the operational and admin layer.

    Without Keto, you'd end up waiting a long time for a change to the application code to reflect the new access control policy. With Keto, you can make the change in the operational layer and have it reflected in the production instantly.

    All that's require with Keto is an API call to the admin endpoint when managing the authorization of your platform at the operational level, using an authorization as a service tool such as Ory Keto.

    Disclaimer

    This blog post is NOT sponsored by Ory(1). I'm just a happy user of their products and I want to share my experience with you.

    1. Though, I definitely wouldn't mind seeing some dollars. 🤑

    How Does Ory Keto Work?

    If you've worked with RBAC systems before, understanding the inner workings of Ory Keto should be a piece of cake for you. 🍰

    It also closely resembles Linux file permissions9, in that you can assign users to groups, and allow them a certain level of access over files and directories.

    To make the matters more clear, we are gonna use an illustration below.

    Bear in mind that this diagram is loaning what we've built before with Ory Kratos and Ory Oathkeeper and you are more than welcome to give those a read as well to have the full picture.

    sequenceDiagram
        participant User as User/Identity
        participant Proxy as Ory Oathkeeper
        participant Auth as Ory Kratos
        participant Authz as Ory Keto
        participant Upstream as Upstream Server
    
        User->>Proxy: Request access
        Proxy->>Auth: Is it authenticated/logged in?
        Auth->>User: Not authenticated: 401 unauthorized
        Proxy->>Authz: Is it authorized?
        Authz->>User: Not authorized: 403 forbidden
        Proxy->>Upstream: Forward request
        Upstream->>User: Response

    Here are the steps, simplified for better understanding:

    1. The request comes into the system, Oathkeeper takes the request.
    2. The Oathkeeper will consult the Kratos to see if the request is authenticated.
    3. If the request is not authenticated, the user will get a 401 Unauthorized response, redirected to the login page, or another action based on the configuration.
    4. If the request is authenticated, the Oathkeeper will consult the Keto to see if the request is authorized.
    5. If the request is not authorized, the user will get a 403 Forbidden response, and the corresponding error message will be displayed.
    6. If the request is authorized, the Oathkeeper will forward the request to the upstream server, waits for the response and returns it to the user.

    This flow will repeat for every request coming into the system. Since all of these services are stateless, you can scale them on-demand; the only bottleneck will be the SQL database running in the background and there different techniques to scale that as well10.

    In our series covering the Ory products, we've already covered all the way until step 3. This blog post will cover the authorization part, step 4.

    At the end of this article, you should be able to protect your upstream server from any unauthorized access, without the need to implement it manually in your code.

    That, in effect, means that you can take any application in the wild, and protect it using only the operational layer, without touching the application, and without even needing to understand or modify its code.

    How to Deploy self-hosted Ory Keto?

    You don't necessarily have to deploy the Keto yourself to take it for a spin11. However, we are feeling nerdy here and can't help it. 🤓

    Additionally, I, personally, have found the Ory's Helm charts12 to be extremely inflexible and hard to customize. You'd expect that using a template engine would allow your downstream users more wiggle room, but the sad reality is that in my opinion, there's a lot of room for improvement in their Helm charts13.

    As such, we are using Kustomization in the following stack:

    Here's the tree structure before getting into the code:

    ./keto/
    ├── deployment.yml
    ├── externalsecret.yml
    ├── httproute-read.yml
    ├── httproute-write.yml
    ├── keto-server-config.yml
    ├── kustomization.yml
    ├── kustomize.yml
    ├── service-read.yml
    └── service-write.yml
    
    keto/deployment.yml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: keto
    spec:
      replicas: 1
      strategy:
        rollingUpdate:
          maxSurge: 1
          maxUnavailable: 0
        type: RollingUpdate
      template:
        spec:
          initContainers:
            - name: keto-automigrate
              image: oryd/keto
              command:
                - keto
              args:
                - migrate
                - up
                - --yes
                - --config
                - /etc/config/keto.yaml
              envFrom:
                - secretRef:
                    name: keto-secrets
              volumeMounts:
                - name: keto-config
                  mountPath: /etc/config/keto.yaml
                  subPath: keto.yaml
                  readOnly: true
          containers:
            - image: oryd/keto
              name: keto
              command:
                - keto
              args:
                - serve
                - --config
                - /etc/config/keto.yaml
              envFrom:
                - secretRef:
                    name: keto-secrets
              ports:
                - containerPort: 4466
                  name: keto-read
                - containerPort: 4467
                  name: keto-write
              volumeMounts:
                - name: keto-config
                  mountPath: /etc/config/keto.yaml
                  subPath: keto.yaml
                  readOnly: true
              resources: {}
          volumes:
            - name: keto-config
              configMap:
                name: keto-config
                optional: false
                items:
                  - key: keto-server-config.yml
                    path: keto.yaml
    
    keto/externalsecret.yml
    apiVersion: external-secrets.io/v1beta1
    kind: ExternalSecret
    metadata:
      name: keto-secrets
    spec:
      data:
        - remoteRef:
            key: /keto/dsn
          secretKey: DSN
      refreshInterval: 24h
      secretStoreRef:
        kind: ClusterSecretStore
        name: aws-parameter-store
    
    keto/httproute-read.yml
    apiVersion: gateway.networking.k8s.io/v1
    kind: HTTPRoute
    metadata:
      name: keto-read
    spec:
      hostnames:
        - acl.developer-friendly.blog
      parentRefs:
        - group: gateway.networking.k8s.io
          kind: Gateway
          name: developer-friendly-blog
          namespace: cert-manager
          sectionName: https
      rules:
        - backendRefs:
            - kind: Service
              name: keto-read
              port: 80
          filters:
            - responseHeaderModifier:
                set:
                  - name: Strict-Transport-Security
                    value: max-age=31536000; includeSubDomains; preload
              type: ResponseHeaderModifier
          matches:
            - path:
                type: PathPrefix
                value: /
    
    keto/httproute-write.yml
    apiVersion: gateway.networking.k8s.io/v1
    kind: HTTPRoute
    metadata:
      name: keto-write
    spec:
      hostnames:
        - acl-admin.developer-friendly.blog
      parentRefs:
        - group: gateway.networking.k8s.io
          kind: Gateway
          name: developer-friendly-blog
          namespace: cert-manager
          sectionName: https
      rules:
        - backendRefs:
            - kind: Service
              name: keto-write
              port: 80
          filters:
            - responseHeaderModifier:
                set:
                  - name: Strict-Transport-Security
                    value: max-age=31536000; includeSubDomains; preload
              type: ResponseHeaderModifier
          matches:
            - path:
                type: PathPrefix
                value: /
    

    The most important part of the Keto server configuration below, besides all the operational configurations and boilerplates, is the namespace definition. More on that in a bit.

    Also note the limit.max_read_depth which allows for the use of RBAC in our system.

    keto/keto-server-config.yml
    limit:
      max_read_depth: 3
    log:
      format: json
      leak_sensitive_values: true
      level: info
    namespaces:
      - id: 0
        name: roles
      - id: 1
        name: endpoints
    profiling: cpu
    serve:
      metrics:
        host: 0.0.0.0
      opl:
        host: 0.0.0.0
      read:
        host: 0.0.0.0
      write:
        host: 0.0.0.0
    tracing:
      provider: jaeger
      providers:
        jaeger:
          local_agent_address: jaeger.monitoring:6831
          sampling:
            server_url: http://jaeger.monitoring:5778/sampling
            trace_id_ratio: 1
      service_name: keto
    
    keto/kustomization.yml
    configMapGenerator:
      - files:
          - ./keto-server-config.yml
        name: keto-config
    
    images:
      - name: oryd/keto
        newTag: v0.12.0
    
    resources:
      - externalsecret.yml
      - service-read.yml
      - service-write.yml
      - deployment.yml
      - httproute-read.yml
      - httproute-write.yml
    
    commonLabels:
      app.kubernetes.io/component: keto
      app.kubernetes.io/instance: keto
      app.kubernetes.io/managed-by: Kustomize
      app.kubernetes.io/name: keto
      app.kubernetes.io/version: v0.12.0
    
    keto/service-read.yml
    apiVersion: v1
    kind: Service
    metadata:
      name: keto-read
    spec:
      ports:
      - name: http
        port: 80
        protocol: TCP
        targetPort: keto-read
      type: ClusterIP
    
    keto/service-write.yml
    apiVersion: v1
    kind: Service
    metadata:
      name: keto-write
    spec:
      ports:
      - name: http
        port: 80
        protocol: TCP
        targetPort: keto-write
      type: ClusterIP
    

    Lastly, let's apply this stack:

    keto/kustomize.yml
    apiVersion: kustomize.toolkit.fluxcd.io/v1
    kind: Kustomization
    metadata:
      name: keto
      namespace: flux-system
    spec:
      force: false
      interval: 5m
      path: ./keto
      prune: true
      sourceRef:
        kind: GitRepository
        name: flux-system
      targetNamespace: auth
      wait: true
    
    kubectl apply -f keto/kustomize.yml
    

    Once this stack is up, we can use the Keto CLI14 to check the connection to the server.

    $ export KETO_READ_REMOTE=acl.developer-friendly.blog:443
    $ export KETO_WRITE_REMOTE=acl-admin.developer-friendly.blog:443
    
    $ keto status
    SERVING
    
    $ keto relation-tuple get
    NAMESPACE       OBJECT  RELATION NAME   SUBJECT
    
    NEXT PAGE TOKEN
    IS LAST PAGE    true
    

    What is the Namespace in Ory Keto?

    We've been putting it off for a while, but it's time to address the elephant in the room 🐘. The namespace in Keto is the most important concept you should be aware of to understand the model of the authorization Keto provides.

    It is this concept that allows you to create a multi-tenant system, where each tenant can have its own set of policies, roles, and permissions.

    It grants the ability to create hierarchical permission structures, RBAC, and ABAC models, and allows you to create a complex set of rules that can be applied to a specific tenant or a group of tenants.

    Let's provide some concrete examples to make it more clear.

    flowchart TB
        Bob([Bob])
        Alice([Alice])
        AuditBot([Audit Bot])
        Posts[/Posts/]
        Users[/Users/]
    
        Bob --> EditorGroup
        Alice --> AdminGroup
        EditorGroup --> |Write| Posts
        AdminGroup --> |Owner| Posts
        AdminGroup --> |Owner| Users
        AuditBot --> |Read-only| Users
    
        classDef permission fill:none,stroke:#333,stroke-width:2px;
    
        class Alice,Bob person;
        class AdminGroup,EditorGroup group;
        class Owner,Edit,Read permission;

    Let's explain some of the highlights of this diagram before putting it all into code.

    • There are two groups of entities in our system, one identity in each.
    • Granting resource access to a group falls under the RBAC category, e.g., AdminGroup to Posts relationship.
    • Granting resource access to a specific identity falls under the ABAC category, e.g., AuditBot to Users relationship.

    It is usually the case that RBAC is a more flexible and desirable permission model, especially at scale.

    ABAC, however, is more granular and can be used to create a more fine-grained permission model, yet it might be harder to maintain over the long run.

    Overall, it's not uncommon to see a combination of both models in a system.

    What Are My Namespaces?

    It's not unusual to get lost in what namespaces you have in your system. It can be a bit tricky to define the boundaries of a namespace.

    However, there is one recipe that can help you understand and define those namespaces and it is this:

    Whenever there is a relationship between a subject/group and an object, there is a namespace.

    For example, in the diagram above, we have two namespaces: roles and endpoints.

    That's because we have relationships between the identities and the the groups we want to assign them to (roles).

    We also have relationships between the groups and the resources we want to grant them access to (endpoints).

    Whenever you get lost in defining your namespaces, remind yourself that the key here is the relationship.

    Let's define the relationships in the following sections. These are the files you are going to see:

    ./permissions/
    ├── admin-rbac.json
    ├── auditbot-rbac.json
    ├── editor-rbac.json
    └── members.json
    

    Role-Based Access Control (RBAC)

    Now that we know what is and isn't a namespace, let's create our groups and their members in the Keto server15.

    permissions/members.json
    [
      {
        "namespace": "roles",
        "object": "admin",
        "relation": "member",
        "subject_id": "[email protected]"
      },
      {
        "namespace": "roles",
        "object": "editor",
        "relation": "member",
        "subject_id": "[email protected]"
      }
    ]
    

    Now, let's grant permissions to our groups.

    permissions/admin-rbac.json
    [
      {
        "namespace": "endpoints",
        "object": "/api/v1/posts",
        "relation": "POST",
        "subject_set": {
          "namespace": "roles",
          "object": "admin",
          "relation": "member"
        }
      },
      {
        "namespace": "endpoints",
        "object": "/api/v1/posts",
        "relation": "GET",
        "subject_set": {
          "namespace": "roles",
          "object": "admin",
          "relation": "member"
        }
      },
      {
        "namespace": "endpoints",
        "object": "/api/v1/users",
        "relation": "POST",
        "subject_set": {
          "namespace": "roles",
          "object": "admin",
          "relation": "member"
        }
      },
      {
        "namespace": "endpoints",
        "object": "/api/v1/users",
        "relation": "GET",
        "subject_set": {
          "namespace": "roles",
          "object": "admin",
          "relation": "member"
        }
      }
    ]
    
    permissions/editor-rbac.json
    [
      {
        "namespace": "endpoints",
        "object": "/api/v1/posts",
        "relation": "POST",
        "subject_set": {
          "namespace": "roles",
          "object": "editor",
          "relation": "member"
        }
      },
      {
        "namespace": "endpoints",
        "object": "/api/v1/posts",
        "relation": "GET",
        "subject_set": {
          "namespace": "roles",
          "object": "editor",
          "relation": "member"
        }
      }
    ]
    

    These permissions define the following relationships:

    • Any identity in the EditorGroup can read and write to the Posts endpoint.
    • Any identity in the AdminGroup can read and write to the Posts endpoint.
    • Any identity in the AdminGroup can read and write to the Users endpoint.

    If we did not define the two namespaces in the Keto server configuration, we'd get a 404 Not Found error when trying to create these permissions.

    NOTE: The values we specified for relation and object are dynamic and can be customized to reflect our business logic. The value of the namespace, however, is static and should be defined in the server configuration beforehand.

    Attribute-Based Access Control (ABAC)

    Let's briefly switch gears and create our single permission for the AuditBot.

    The idea is that our AuditBot should be able to view the users of the system. This will allow for auditing the system without having to have access to the user's data.

    permissions/auditbot-abac.json
    [
      {
        "namespace": "endpoints",
        "object": "/api/v1/users",
        "relation": "GET",
        "subject_id": "[email protected]"
      }
    ]
    

    Here, we are not adding the audit bot to any group. Instead, we are granting the permission directly to the identity.

    The reason why ABAC can be harder to maintain is that you have to keep track of all the identities and their permissions in the system. This can be a daunting task as the number of identities in a system grows.

    These permissions and relationships can be created via either HTTP request to the Keto server admin API (as you see below), or from inside your application code using the available SDKs16.

    $ keto relation-tuple create ./permissions/
    NAMESPACE       OBJECT          RELATION NAME   SUBJECT
    endpoints       /api/v1/posts   POST            roles:admin#member
    endpoints       /api/v1/posts   GET             roles:admin#member
    endpoints       /api/v1/users   POST            roles:admin#member
    endpoints       /api/v1/users   GET             roles:admin#member
    endpoints       /api/v1/users   GET             [email protected]
    endpoints       /api/v1/posts   POST            roles:editor#member
    endpoints       /api/v1/posts   GET             roles:editor#member
    roles           admin           member          [email protected]
    roles           editor          member          [email protected]
    

    For example, if a new user signs up to the application, you can decide whether or not to add them to a specific group, or grant them a certain permission.

    After this initial permission creation, Ory products will take care of the authentication and authorization for you, without any request ever reaching your application if it is not meant to.

    Query the Permission Engine

    We have created our demo permissions and groups. Now, let's verify that the permissions are working as expected.

    We will combine the the three flagship products of Ory for an integrated auth solution in a bit, but let's query the Keto server directly for now 17.

    curl -X POST \
      https://acl.developer-friendly.blog/relation-tuples/check \
      -Hcontent-type:application/json \
      -d'{"namespace":"endpoints",
          "object":"/api/v1/users",
          "relation":"POST",
          "subject_id":"[email protected]"}' \
      -D -
    

    The result of this query will be a 200 OK with the following response:

    {"allowed":true}
    

    Noticed the beauty? The query is asking for a write permission for an email address we never explicitly granted access.

    However, the Keto permission and policy engine will recurse through the groups until the maximum of predefined max-depth is reached18. If no permission matches, a 403 Forbidden will be returned.

    In the same HTTP query, you can specify max-depth as query parameter, however the hard limit of that number is still the configured global value19.

    flowchart TB
        alice([[email protected]])
        Users[/Users/]
    
        alice --> |Member of| AdminGroup["Admin Group"]
        AdminGroup --> |Write permission| Users

    Verify the Permissions and Access Control

    As before, we're heavily relying on our previously built stack on the Ory series. If you need a refresher, give them a look before proceeding.

    Let's build the Oathkeeper rule as a Kubernetes CRD to consult the Keto server for the permissions.

    Applying the following CRD resources, the Oathkeeper Maester20 will inform the Oathkeeper server using the following process:

    1. Update the corresponding ConfigMap with the new rules.
    2. Upon that update, the mounted volume will trigger a reload in the Oathkeeper server, resulting in the new rules being applied.
    rules/echo-server.yml
    apiVersion: oathkeeper.ory.sh/v1alpha1
    kind: Rule
    metadata:
      name: echo-server
    spec:
      authenticators:
        - handler: cookie_session
          config:
            check_session_url: http://kratos-public.auth/sessions/whoami
            extra_from: "@this"
            force_method: GET
            only:
              - ory_kratos_session
            subject_from: identity.id
      authorizer:
        config:
          remote: http://keto-read.auth/relation-tuples/check
          payload: |
            {
              "namespace": "endpoints",
              "object": "{{ print .MatchContext.URL.EscapedPath }}",
              "relation": "{{ print .MatchContext.Method }}",
              "subject_id": "{{ print .Extra.identity.traits.email }}"
            }
        handler: remote_json
      errors:
        - handler: json
      match:
        methods:
          - POST
          - PUT
          - DELETE
          - PATCH
          - GET
        url: https://echo.developer-friendly.blog/api/v1/users<.*>
      mutators:
        - handler: header
          config:
            headers:
              x-user-id: "{{ print .Subject }}"
              x-user-email: "{{ print .Extra.identity.traits.email }}"
      upstream:
        preserveHost: true
        url: http://echo-server.default
    

    Let's break down this rule for a better understanding.

    Authenticator Handler (Kratos)

    We have discussed the authenticator handler in our Kratos blog post. Briefly, the authenticator handler is responsible for checking whether or not the request is logged in.

    Authorizer Handler (Keto)

    The authorizer config you see in this rule is exactly identical to the curl command we had earlier. The only difference being that with Kubernetes CRD, we are consulting the Keto server using the in-cluster Kubernetes Service address.

      authorizer:
        config:
          remote: http://keto-read.auth/relation-tuples/check
          payload: |
            {
              "namespace": "endpoints",
              "object": "{{ print .MatchContext.URL.EscapedPath }}",
              "relation": "{{ print .MatchContext.Method }}",
              "subject_id": "{{ print .Extra.identity.traits.email }}"
            }
        handler: remote_json
    
    curl -X POST \
      https://acl.developer-friendly.blog/relation-tuples/check \
      -Hcontent-type:application/json \
      -d'{"namespace":"endpoints",
          "object":"/api/v1/users",
          "relation":"POST",
          "subject_id":"[email protected]"}' \
      -D -
    

    The placeholder in the authorizer rule is benefiting from the Go template language as specified in the official Ory documentation21 & the Official Go net/url package22.

    Matching Strategy (Oathkeeper)

    The match clause makes sure to only apply this rule to the HTTP requests targeting this address:

    https://echo.developer-friendly.blog/api/v1/users<.*>

    Notice that the <.*> is a regex pattern that is only effective if access_rules.matching_strategy: regexp in Oathkeeper configuration is set.

    For an HTTP request, this matching is quite comprehensive:

    rules/echo-server.yml
      match:
        methods:
          - POST
          - PUT
          - DELETE
          - PATCH
          - GET
        url: https://echo.developer-friendly.blog/api/v1/users<.*>
    

    Mutator Handler (Oathkeeper)

    This is an optional field. You can decide to add handler: noop to avoid the rule touching the request.

    However, adding the subject ID or any other header to the request can be hugely benefitial to your upstream servers since they will have access to these information without paying the extra cost of a network roundtrip or a database query.

    rules/echo-server.yml
      mutators:
        - handler: header
          config:
            headers:
              x-user-id: "{{ print .Subject }}"
              x-user-email: "{{ print .Extra.identity.traits.email }}"
    

    Upstream Handler (Oathkeeper)

    The last part of this equation is to forward the request to the upstream server. You can choose to keep the HTTP Host header or change it to the value specified in your Rule CRD.

    Since the value of the Host is almost always coming from the end-user, it is a good idea to keep it as is, unless you have a specific use-case to change it.

    rules/echo-server.yml
      upstream:
        preserveHost: true
        url: http://echo-server.default
    

    Beware that the value of the upstream.url consists of the Kubernetes Service name in the format of http://<service-name>.<namespace>.svc.cluster.local. The last three are optional!

    This echo-server is a Rust 🦀 application written by us. Take a look at our previous article for its deployment definition.

    Jaeger

    If you configure the tracing of Ory products, you will have a view in your Jaeger dashboard similar to what you see below.

    Jaeger Dashboard
    Jaeger Dashboard

    Conclusion

    In this blog post, we've configured our Keto server to handle the authorization of our application. With our previously deployed Kratos server and Oathkeeper, we managed to protect our upstream service from all the unauthenticated and unauthorized requests.

    With the knowledge you have gathered here, you are well on your way to be able to build a secure, scalable, and maintainable application, while still being able to keep the operational aspect of your application outside the source code, hugely benefiting yourself, your team and the entire long-term success of your product.

    These days, I rarely try to build my own authentication and authorization into the application. There are many great tools out there that has truly passed the test of time and are battle-tested.

    Using either of these tools greatly simplifies your processes and I, for one, am one to believe that "our auth need are very special and need in-house development" is nothing but a load of baloney. 💩

    I would recommend everyone in the industry to at least give Ory products a fair shot before trying to do a sloppy work at reinventing the wheel and shooting themselves in the foot. 🔫

    You may be surprised how comprehensive their suite of products are and how they can help you build your app faster and worry about nonsensical aspects less.

    I hope you have enjoyed this article as well as I did writing it.

    Happy hacking and until next time 🫡, ciao. 🐧 🦀

    If you enjoyed this blog post, consider sharing it with these buttons 👇. Please leave a comment for us at the end, we read & love 'em all. ❣

    Share on Share on Share on Share on

    Comments