Your data, your control. Fully open source, authentication and authorization. No lock-ins. Deployment in Railway in 120 seconds || Spin a docker image as a micro-service in your infra. Built in login page and Admin panel out of the box.
1,999
stars
1,604
commits
Go
primary language
Sep 4, 2026
updated
Open-source authentication and authorization for your applications.
Bring your own database and stay in control of user data.
Documentation · OAuth 2.0 / OIDC · v1 → v2 migration · Contributing · Discord
Authorizer is an open-source authentication and authorization server you can self-host. Connect any supported database (13+ backends including Postgres, MySQL, SQLite, SQL Server, YugaByte, MariaDB, Cassandra, ScyllaDB, MongoDB, ArangoDB, DynamoDB, and Couchbase) and run OAuth2/OIDC, social login, MFA, magic links, RBAC, webhooks, and email templates from one place.
v2 note: Authorizer v2 uses CLI arguments for all configuration. The server does not read from
.envor OS environment variables. Pass config when starting the binary (e.g../authorizer --client-id=... --client-secret=...). See MIGRATION.md.
Prerequisites: Go ≥ 1.24 (see go.mod).
git clone https://github.com/authorizerdev/authorizer.git
cd authorizer
make dev
make dev runs the server with SQLite and development defaults (RS256 keys, sample client credentials). Open the URL printed in the logs (default port 8080) and sign in with --admin-secret (admin in dev).
For production builds, tests, and Docker, see Getting Started below.
client_credentials grant and secretless workload identity (RFC 7523 client_assertion, Kubernetes projected ServiceAccount tokens, TokenReview). Works out of the box on EKS, GKE and AKS, which publish a public OIDC issuer and JWKS by default. Clusters left on the default issuer (kubernetes.default.svc, e.g. kubeadm/kind) publish private addresses that Authorizer's SSRF guard refuses — point jwks_url at a reachable mirror of /openid/v1/jwks (key_source_type: static_jwks_url); issuer_url only has to match the token's iss and is never fetched. Verified by make test-k8s. SPIFFE JWT-SVID is preview: its draft (draft-schwenkschuster-oauth-spiffe-client-auth-00) expired 2026-01-02, is not WG-adopted, and its assertion-type URN is not IANA-registered, so the value may changeact chains and scope attenuationShipped
authorizer-py v0.2.0 (v0.3.0 in pre-release: pip install --pre authorizer-py); sync + async clients, admin APIrc tag); protocol prop, pre-built login/signup componentsPlanned
Deploy production ready Authorizer instance using one click deployment options available below
| Infra provider | One-click link | Additional information |
|---|---|---|
| Railway.app | docs | |
| Heroku | docs | |
| Render | docs | |
| Koyeb | docs | |
| RepoCloud | ![]() | docs |
| Alibaba Cloud | docs |
This guide helps you practice using Authorizer to evaluate it before you use it in a production environment. It includes instructions for installing the Authorizer server in local or standalone mode.
go.mod)git clone https://github.com/authorizerdev/authorizer.git (or your fork URL)cd authorizermake dev — SQLite, RS256 dev keys, sample OAuth client (see Quick start)make build (or go build -o build/authorizer .); optionally make build-app and make build-dashboardmake dev:./build/authorizer \
--database-type=sqlite \
--database-url=test.db \
--url=http://localhost:8080 \
--jwt-type=HS256 \
--jwt-secret=test \
--encryption-key=test-encryption-key \
--admin-secret=admin \
--client-id=123456 \
--client-secret=secret
v2: The server does not read from
.env. All configuration must be passed as CLI arguments. See MIGRATION.md for the full mapping of env vars to flags.
The default image runs as non-root (UID 65532). Writable mounts (SQLite under /authorizer/data, etc.) are usually root-owned, so pick one of:
Run as root for that container (simplest for local SQLite + volumes):
docker run -p 8080:8080 -u root \
-v authorizer_data:/authorizer/data \
quay.io/authorizer/authorizer \
--database-type=sqlite \
--database-url=/authorizer/data/data.db \
--url=http://localhost:8080 \
--client-id=123456 \
--client-secret=secret \
--admin-secret=admin \
--jwt-type=HS256 \
--jwt-secret=test \
--encryption-key=test-encryption-key
Keep non-root and make the mount writable by 65532 (good for production-style bind mounts):
mkdir -p ./data && sudo chown -R 65532:65532 ./data
docker run -p 8080:8080 \
-v "$(pwd)/data:/authorizer/data" \
quay.io/authorizer/authorizer \
--database-type=sqlite \
--database-url=/authorizer/data/data.db \
--url=http://localhost:8080 \
...
Build from source with the root target (no -u at run time):
docker build --target final-root -t authorizer:root .
docker run -p 8080:8080 -v authorizer_data:/authorizer/data authorizer:root \
--database-type=sqlite --database-url=/authorizer/data/data.db ...
-p 8080:8080 to expose it.authorizer_data persists the SQLite DB; use a named volume or a host path (e.g. -v $(pwd)/data:/authorizer/data).ENTRYPOINT ["./authorizer"] so args after the image name go to the binary). See MIGRATION.md for the full list of flags.Inside a container, localhost / 127.0.0.1 is the container itself, not your machine. Use a host alias instead:
Docker Desktop (macOS / Windows): use host.docker.internal in --database-url or --database-host (built in).
docker run -p 8080:8080 quay.io/authorizer/authorizer \
--database-type=postgres \
--database-url="postgres://user:pass@host.docker.internal:5432/dbname?sslmode=disable" \
...
Linux (Docker Engine): add the same hostname so it resolves to the host:
docker run -p 8080:8080 --add-host=host.docker.internal:host-gateway \
quay.io/authorizer/authorizer \
--database-type=postgres \
--database-url="postgres://user:pass@host.docker.internal:5432/dbname?sslmode=disable" \
...
Alternative on Linux: use the docker bridge gateway IP (often 172.17.0.1) if your DB listens on 0.0.0.0, or run with --network host so the container shares the host network (then localhost works; port mapping -p is not used the same way).
Ensure the database accepts non-localhost connections (e.g. listen_addresses in Postgres, bind address in MySQL) and that your OS firewall allows the Docker subnet.
Extending the image with env-based config (e.g. Railway): If you FROM quay.io/authorizer/authorizer and use a shell-form CMD so that env vars are expanded at runtime, you must override ENTRYPOINT in your Dockerfile or the binary will receive /bin/sh and -c as arguments and fail. Use:
FROM quay.io/authorizer/authorizer:2.0.0-rc.1
# v2 uses CLI arguments only. Railway (etc.) inject env vars; shell form CMD expands them at runtime.
# Override ENTRYPOINT so CMD is run by a shell; otherwise the base ENTRYPOINT would receive /bin/sh -c "..." as args.
ENTRYPOINT ["/bin/sh", "-c"]
CMD ./authorizer \
--database-type="$${DATABASE_TYPE:-postgres}" \
--database-url="$${DATABASE_URL}" \
--url="$${AUTHORIZER_URL}" \
--client-id="$${CLIENT_ID}" \
--client-secret="$${CLIENT_SECRET}" \
--admin-secret="$${ADMIN_SECRET}" \
...
Use $$ in the Dockerfile so Docker does not expand $VAR at build time.
Deploy / Try Authorizer using binaries. With each Authorizer Release, binaries are baked with required deployment files and bundled. You can download a specific version for the following operating systems:
Note: For Windows, we recommend running Authorizer via Docker.
tar -zxf authorizer-VERSION-OS-ARCH.tar.gz
cd authorizer-VERSION-OS-ARCH
./authorizer \
--database-type=sqlite \
--database-url=test.db \
--url=http://localhost:8080 \
--jwt-type=HS256 \
--jwt-secret=test \
--encryption-key=test-encryption-key \
--admin-secret=admin \
--client-id=123456 \
--client-secret=secret
v2: The binary is named
authorizer(notserver). Configuration is passed via CLI arguments;.envis not read. On macOS you may need:xattr -d com.apple.quarantine authorizer
--admin-secret you configured at startupv2: Environment variables are not configurable from the dashboard. All configuration is set at startup via CLI arguments. See MIGRATION.md for the full list of flags.
Note: One can always disable the email verification to allow open sign up, which is not recommended for production as anyone can use anyone's email address 😅
This example demonstrates how you can use [@authorizerdev/authorizer-js](/authorizer-js/getting-started) CDN version and have login ready for your site in few seconds. You can also use the ES module version of [@authorizerdev/authorizer-js](/authorizer-js/getting-started) or framework-specific versions like [@authorizerdev/authorizer-react](/authorizer-react/getting-started)
html fileNote: Change AUTHORIZER_URL in the below code with your authorizer URL. Also, you can change the logout button component
<script src="https://unpkg.com/@authorizerdev/authorizer-js/lib/authorizer.min.js"></script>
<script type="text/javascript">
const authorizerRef = new authorizerdev.Authorizer({
authorizerURL: `YOUR_AUTHORIZER_INSTANCE_URL`,
redirectURL: window.location.origin,
clientID: 'YOUR_CLIENT_ID', // value of --client-id flag used to start the server
});
// use the button selector as per your application
const logoutBtn = document.getElementById('logout');
logoutBtn.addEventListener('click', async function () {
await authorizerRef.logout();
window.location.href = '/';
});
async function onLoad() {
const res = await authorizerRef.authorize({
response_type: 'code',
use_refresh_token: false,
});
if (res && res.access_token) {
// you can use user information here, eg:
const user = await authorizerRef.getProfile({
Authorization: `Bearer ${res.access_token}`,
});
const userSection = document.getElementById('user');
const logoutSection = document.getElementById('logout-section');
logoutSection.classList.toggle('hide');
userSection.innerHTML = `Welcome, ${user.email}`;
}
}
onLoad();
</script>
(top 30 of 36)
Go
78.6%
TypeScript
19.6%
Your data, your control. Fully open source, authentication and authorization. No lock-ins. Deployment in Railway in 120 seconds || Spin a docker image as a micro-service in your infra. Built in login page and Admin panel out of the box.
1,999
stars
1,604
commits
Go
primary language
Sep 4, 2026
updated
Open-source authentication and authorization for your applications.
Bring your own database and stay in control of user data.
Documentation · OAuth 2.0 / OIDC · v1 → v2 migration · Contributing · Discord
Authorizer is an open-source authentication and authorization server you can self-host. Connect any supported database (13+ backends including Postgres, MySQL, SQLite, SQL Server, YugaByte, MariaDB, Cassandra, ScyllaDB, MongoDB, ArangoDB, DynamoDB, and Couchbase) and run OAuth2/OIDC, social login, MFA, magic links, RBAC, webhooks, and email templates from one place.
v2 note: Authorizer v2 uses CLI arguments for all configuration. The server does not read from
.envor OS environment variables. Pass config when starting the binary (e.g../authorizer --client-id=... --client-secret=...). See MIGRATION.md.
Prerequisites: Go ≥ 1.24 (see go.mod).
git clone https://github.com/authorizerdev/authorizer.git
cd authorizer
make dev
make dev runs the server with SQLite and development defaults (RS256 keys, sample client credentials). Open the URL printed in the logs (default port 8080) and sign in with --admin-secret (admin in dev).
For production builds, tests, and Docker, see Getting Started below.
client_credentials grant and secretless workload identity (RFC 7523 client_assertion, Kubernetes projected ServiceAccount tokens, TokenReview). Works out of the box on EKS, GKE and AKS, which publish a public OIDC issuer and JWKS by default. Clusters left on the default issuer (kubernetes.default.svc, e.g. kubeadm/kind) publish private addresses that Authorizer's SSRF guard refuses — point jwks_url at a reachable mirror of /openid/v1/jwks (key_source_type: static_jwks_url); issuer_url only has to match the token's iss and is never fetched. Verified by make test-k8s. SPIFFE JWT-SVID is preview: its draft (draft-schwenkschuster-oauth-spiffe-client-auth-00) expired 2026-01-02, is not WG-adopted, and its assertion-type URN is not IANA-registered, so the value may changeact chains and scope attenuationShipped
authorizer-py v0.2.0 (v0.3.0 in pre-release: pip install --pre authorizer-py); sync + async clients, admin APIrc tag); protocol prop, pre-built login/signup componentsPlanned
Deploy production ready Authorizer instance using one click deployment options available below
| Infra provider | One-click link | Additional information |
|---|---|---|
| Railway.app | docs | |
| Heroku | docs | |
| Render | docs | |
| Koyeb | docs | |
| RepoCloud | ![]() | docs |
| Alibaba Cloud | docs |
This guide helps you practice using Authorizer to evaluate it before you use it in a production environment. It includes instructions for installing the Authorizer server in local or standalone mode.
go.mod)git clone https://github.com/authorizerdev/authorizer.git (or your fork URL)cd authorizermake dev — SQLite, RS256 dev keys, sample OAuth client (see Quick start)make build (or go build -o build/authorizer .); optionally make build-app and make build-dashboardmake dev:./build/authorizer \
--database-type=sqlite \
--database-url=test.db \
--url=http://localhost:8080 \
--jwt-type=HS256 \
--jwt-secret=test \
--encryption-key=test-encryption-key \
--admin-secret=admin \
--client-id=123456 \
--client-secret=secret
v2: The server does not read from
.env. All configuration must be passed as CLI arguments. See MIGRATION.md for the full mapping of env vars to flags.
The default image runs as non-root (UID 65532). Writable mounts (SQLite under /authorizer/data, etc.) are usually root-owned, so pick one of:
Run as root for that container (simplest for local SQLite + volumes):
docker run -p 8080:8080 -u root \
-v authorizer_data:/authorizer/data \
quay.io/authorizer/authorizer \
--database-type=sqlite \
--database-url=/authorizer/data/data.db \
--url=http://localhost:8080 \
--client-id=123456 \
--client-secret=secret \
--admin-secret=admin \
--jwt-type=HS256 \
--jwt-secret=test \
--encryption-key=test-encryption-key
Keep non-root and make the mount writable by 65532 (good for production-style bind mounts):
mkdir -p ./data && sudo chown -R 65532:65532 ./data
docker run -p 8080:8080 \
-v "$(pwd)/data:/authorizer/data" \
quay.io/authorizer/authorizer \
--database-type=sqlite \
--database-url=/authorizer/data/data.db \
--url=http://localhost:8080 \
...
Build from source with the root target (no -u at run time):
docker build --target final-root -t authorizer:root .
docker run -p 8080:8080 -v authorizer_data:/authorizer/data authorizer:root \
--database-type=sqlite --database-url=/authorizer/data/data.db ...
-p 8080:8080 to expose it.authorizer_data persists the SQLite DB; use a named volume or a host path (e.g. -v $(pwd)/data:/authorizer/data).ENTRYPOINT ["./authorizer"] so args after the image name go to the binary). See MIGRATION.md for the full list of flags.Inside a container, localhost / 127.0.0.1 is the container itself, not your machine. Use a host alias instead:
Docker Desktop (macOS / Windows): use host.docker.internal in --database-url or --database-host (built in).
docker run -p 8080:8080 quay.io/authorizer/authorizer \
--database-type=postgres \
--database-url="postgres://user:pass@host.docker.internal:5432/dbname?sslmode=disable" \
...
Linux (Docker Engine): add the same hostname so it resolves to the host:
docker run -p 8080:8080 --add-host=host.docker.internal:host-gateway \
quay.io/authorizer/authorizer \
--database-type=postgres \
--database-url="postgres://user:pass@host.docker.internal:5432/dbname?sslmode=disable" \
...
Alternative on Linux: use the docker bridge gateway IP (often 172.17.0.1) if your DB listens on 0.0.0.0, or run with --network host so the container shares the host network (then localhost works; port mapping -p is not used the same way).
Ensure the database accepts non-localhost connections (e.g. listen_addresses in Postgres, bind address in MySQL) and that your OS firewall allows the Docker subnet.
Extending the image with env-based config (e.g. Railway): If you FROM quay.io/authorizer/authorizer and use a shell-form CMD so that env vars are expanded at runtime, you must override ENTRYPOINT in your Dockerfile or the binary will receive /bin/sh and -c as arguments and fail. Use:
FROM quay.io/authorizer/authorizer:2.0.0-rc.1
# v2 uses CLI arguments only. Railway (etc.) inject env vars; shell form CMD expands them at runtime.
# Override ENTRYPOINT so CMD is run by a shell; otherwise the base ENTRYPOINT would receive /bin/sh -c "..." as args.
ENTRYPOINT ["/bin/sh", "-c"]
CMD ./authorizer \
--database-type="$${DATABASE_TYPE:-postgres}" \
--database-url="$${DATABASE_URL}" \
--url="$${AUTHORIZER_URL}" \
--client-id="$${CLIENT_ID}" \
--client-secret="$${CLIENT_SECRET}" \
--admin-secret="$${ADMIN_SECRET}" \
...
Use $$ in the Dockerfile so Docker does not expand $VAR at build time.
Deploy / Try Authorizer using binaries. With each Authorizer Release, binaries are baked with required deployment files and bundled. You can download a specific version for the following operating systems:
Note: For Windows, we recommend running Authorizer via Docker.
tar -zxf authorizer-VERSION-OS-ARCH.tar.gz
cd authorizer-VERSION-OS-ARCH
./authorizer \
--database-type=sqlite \
--database-url=test.db \
--url=http://localhost:8080 \
--jwt-type=HS256 \
--jwt-secret=test \
--encryption-key=test-encryption-key \
--admin-secret=admin \
--client-id=123456 \
--client-secret=secret
v2: The binary is named
authorizer(notserver). Configuration is passed via CLI arguments;.envis not read. On macOS you may need:xattr -d com.apple.quarantine authorizer
--admin-secret you configured at startupv2: Environment variables are not configurable from the dashboard. All configuration is set at startup via CLI arguments. See MIGRATION.md for the full list of flags.
Note: One can always disable the email verification to allow open sign up, which is not recommended for production as anyone can use anyone's email address 😅
This example demonstrates how you can use [@authorizerdev/authorizer-js](/authorizer-js/getting-started) CDN version and have login ready for your site in few seconds. You can also use the ES module version of [@authorizerdev/authorizer-js](/authorizer-js/getting-started) or framework-specific versions like [@authorizerdev/authorizer-react](/authorizer-react/getting-started)
html fileNote: Change AUTHORIZER_URL in the below code with your authorizer URL. Also, you can change the logout button component
<script src="https://unpkg.com/@authorizerdev/authorizer-js/lib/authorizer.min.js"></script>
<script type="text/javascript">
const authorizerRef = new authorizerdev.Authorizer({
authorizerURL: `YOUR_AUTHORIZER_INSTANCE_URL`,
redirectURL: window.location.origin,
clientID: 'YOUR_CLIENT_ID', // value of --client-id flag used to start the server
});
// use the button selector as per your application
const logoutBtn = document.getElementById('logout');
logoutBtn.addEventListener('click', async function () {
await authorizerRef.logout();
window.location.href = '/';
});
async function onLoad() {
const res = await authorizerRef.authorize({
response_type: 'code',
use_refresh_token: false,
});
if (res && res.access_token) {
// you can use user information here, eg:
const user = await authorizerRef.getProfile({
Authorization: `Bearer ${res.access_token}`,
});
const userSection = document.getElementById('user');
const logoutSection = document.getElementById('logout-section');
logoutSection.classList.toggle('hide');
userSection.innerHTML = `Welcome, ${user.email}`;
}
}
onLoad();
</script>
(top 30 of 36)
Go
78.6%
TypeScript
19.6%