How to Configure Authentication with OAuth2 for Self-Hosted Apps

Configure OAuth2 authentication for self-hosted applications by deploying Ory Hydra as your OAuth2 server, connecting it to your identity provider via a custom login-and-consent flow, and securing your APIs with signed JWT tokens.

OAuth2 has become the de facto standard for securing self-hosted applications and delegating authentication to external identity providers. The mikeroyal/Self-Hosting-Guide repository recommends Ory Hydra as the core OAuth2 server for self-hosted environments. This guide explains how to configure a complete OAuth2 authentication flow using Hydra's identity-provider-agnostic architecture.

Architecture Overview

A typical OAuth2 deployment for self-hosted apps follows a three-component architecture. The Client App (your web or mobile application) redirects users to Ory Hydra (the OAuth2 server), which delegates actual authentication to your chosen Identity Provider (such as Ory Kratos, LDAP, or Keycloak).

This separation of concerns allows Hydra to remain stateless while handling token security. According to the README.md in the Self-Hosting-Guide, Hydra provides a fully-featured OpenID Connect implementation that issues signed JWTs without storing session data internally.

Deploy Ory Hydra with Docker Compose

Begin by deploying Hydra using Docker Compose. The service requires a PostgreSQL database for persistence and specific environment variables to configure your login and consent endpoints.

Create a docker-compose.yml file based on the configuration recommended in the guide:

version: "3.8"
services:
  hydra:
    image: oryd/hydra:v2.2
    environment:
      - DSN=postgres://hydra:secret@db/hydra?sslmode=disable
      - URLS_SELF_ISSUER=http://localhost:4444/
      - URLS_CONSENT=http://localhost:3000/consent
      - URLS_LOGIN=http://localhost:3000/login
      - SECRETS_SYSTEM=very-secret-system-key
    ports:
      - "4444:4444"   # Public API

      - "4445:4445"   # Admin API

    depends_on:
      - db

  db:
    image: postgres:13
    environment:
      - POSTGRES_USER=hydra
      - POSTGRES_PASSWORD=secret
      - POSTGRES_DB=hydra
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

Start the services with docker-compose up -d. Hydra exposes port 4444 for public OAuth2 endpoints and 4445 for administrative operations.

Register Your Client Application

Before accepting authentication requests, you must register your self-hosted app as an OAuth2 client. Use the Hydra CLI inside the running container to create the client with appropriate grant types and scopes.

Run this command after the container is up:

docker exec -i $(docker ps -q -f "ancestor=oryd/hydra") \
  hydra clients create \
  --endpoint http://localhost:4445 \
  --id my-selfhosted-app \
  --secret super-secret \
  --grant-types authorization_code,refresh_token \
  --response-types code \
  --scope openid,offline \
  --callbacks http://localhost:8080/callback

This registers a client with the ID my-selfhosted-app that supports the authorization code flow with refresh tokens. The --callbacks parameter specifies where Hydra redirects users after authentication.

Hydra is identity-provider agnostic—it delegates user authentication to a separate login-and-consent application that you build. This app handles credential verification against your identity store and asks users to grant requested scopes.

Login Endpoint

Your login app receives a login_challenge parameter from Hydra at the /login endpoint. After verifying credentials against your IdP (such as Ory Kratos or LDAP), accept the login request via Hydra's admin API:

const express = require('express')
const fetch = require('node-fetch')
const app = express()

app.get('/login', async (req, res) => {
  const { login_challenge } = req.query
  await fetch('http://localhost:4445/oauth2/auth/requests/login/accept', {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ subject: 'user-id-123', remember: true })
  })
  const data = await (await fetch(`http://localhost:4445/oauth2/auth/requests/login?login_challenge=${login_challenge}`)).json()
  res.redirect(data.redirect_to)
})

Similarly, the consent endpoint receives a consent_challenge and must explicitly grant scopes:

app.get('/consent', async (req, res) => {
  const { consent_challenge } = req.query
  await fetch('http://localhost:4445/oauth2/auth/requests/consent/accept', {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      grant_scope: ['openid', 'offline'],
      remember: true,
      session: { access_token: {}, id_token: {} }
    })
  })
  const data = await (await fetch(`http://localhost:4445/oauth2/auth/requests/consent?consent_challenge=${consent_challenge}`)).json()
  res.redirect(data.redirect_to)
})

app.listen(3000, () => console.log('Login/Consent app listening on :3000'))

Run this Node.js application on port 3000 to match the URLS_LOGIN and URLS_CONSENT values configured in your Hydra environment.

Secure Your APIs with Token Validation

Once the flow completes, client applications receive access tokens to call your protected APIs. Validate these tokens by checking the JWT signature or using Hydra's introspection endpoint.

Test access to a protected resource using the token:

curl -H "Authorization: Bearer <access_token>" http://api.myselfhosted.local/secure-data

Key Configuration Files

The Self-Hosting-Guide references several files that support this configuration:

  • README.md – Contains the Ory Hydra entry listing it among recommended self-hosted services for OAuth2 authentication.
  • Getting Started with Self-Hosting.dockerfile – Demonstrates Docker image building patterns applicable to Hydra deployments and custom login-consent UIs.

Security and Scalability Considerations

Hydra implements several architectural patterns that benefit self-hosted deployments:

  • Statelessness – No session data stored in Hydra; all state lives in PostgreSQL or your chosen database backend.
  • Token Security – Uses signed JWTs for access, refresh, and ID tokens ensuring tamper-proof credentials.
  • High Throughput – Designed for low-latency deployments and can run behind reverse proxies like Traefik (referenced in the guide's service list).
  • OpenID Connect Compliance – Certified OIDC implementation ensures compatibility with major identity providers including Google and GitHub for future federation.

Summary

  • Deploy Ory Hydra using Docker Compose with PostgreSQL for stateless OAuth2 token issuance.
  • Register client applications using the Hydra CLI with specific grant types and callback URLs.
  • Build a custom login-and-consent app that authenticates users against your identity provider and communicates with Hydra's admin API.
  • Secure APIs by validating JWT access tokens issued by Hydra.
  • Reference the README.md in mikeroyal/Self-Hosting-Guide for the complete service catalog and deployment patterns.

Frequently Asked Questions

What is the difference between Ory Hydra and a complete identity provider?

Ory Hydra is an OAuth2 and OpenID Connect server that issues tokens but does not handle user authentication itself. It requires a separate login-and-consent application to verify credentials against your identity store (such as Ory Kratos, LDAP, or Keycloak). This separation allows you to customize the authentication experience while relying on Hydra for secure token management.

Can I run Ory Hydra without Docker?

Yes, though the Self-Hosting-Guide recommends Docker for consistency. You can run Hydra as a binary with the same environment variables (DSN, URLS_SELF_ISSUER, SECRETS_SYSTEM) and command-line flags. Ensure your database (PostgreSQL, MySQL, or SQLite) is accessible and properly configured in the DSN connection string.

How do I handle user sessions in a self-hosted OAuth2 setup?

Hydra does not store session data internally. Instead, configure your login-and-consent application to maintain sessions using cookies or JWTs. When a user returns, Hydra sends a new login challenge; your app can recognize the session cookie and immediately accept the request without re-authenticating, creating a seamless single sign-on experience.

Which scopes should I configure for a typical self-hosted application?

For standard applications, configure openid for OIDC compliance and offline to enable refresh tokens. Additional custom scopes (such as read:profile or admin) can be defined based on your API's authorization requirements. Always request the minimal set of scopes necessary for the application's functionality.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →