Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .optimize-cache.json
Original file line number Diff line number Diff line change
Expand Up @@ -1587,6 +1587,45 @@
"static/images/docs/network/edges-map.png": "12ecc1ea200905ba75eb7cfd17055a156fd51fccf746869a1058f923dfd7ac1b",
"static/images/docs/network/pops-map.png": "205ead599703cf47d0df316db8fcc4f48d5eed01508109fc740d17914275e9ab",
"static/images/docs/network/regions-map.png": "c65f1423ab19c3048bf8bf93117e8f2e1d13a2bc705c00307de7ee821e5668a1",
"static/images/docs/oauth-server/dark/diagram-authorization.png": "5f82a31fb0ec0b753b7b0a2dc6b4f044609847c870205cc32e9ee87767db2d8f",
"static/images/docs/oauth-server/dark/diagram-clients.png": "00de79e7ef87e325309ae33c34a21395b4f3906bfa0809698f63214e3b9457ef",
"static/images/docs/oauth-server/dark/diagram-device-flow.png": "e555617a52b852a33df22d2c991ee8948e0e64599775efea533327cf79d8e37c",
"static/images/docs/oauth-server/dark/diagram-overview.png": "cf64794cfb2d0204d4cff2f0061923a73f5d77a8333bfba6ddb693f28a80671d",
"static/images/docs/oauth-server/dark/diagram-scopes.png": "9e1aeae9645bb68b581881596393db446402f76ecb4740dd05aca69536324876",
"static/images/docs/oauth-server/dark/diagram-tokens.png": "060e52ff548a7a337af7a62ac89f0f832034255cf4d3b3652d15384b989622fd",
"static/images/docs/oauth-server/dark/oauth2-server-apps-empty.png": "b81617a77c6489bc93cfe3c118c2fd075eebda850fc5803cba4b1b62792269f1",
"static/images/docs/oauth-server/dark/oauth2-server-apps-list.png": "a0b1a9bcc5708bb48f7d4684d754cfc8af8e666f808e11d00fc42182ee7ccf6e",
"static/images/docs/oauth-server/dark/oauth2-server-create-app.png": "7ecd5625aa964ab77b87440e52bc866a4894dffe5183a0e86f7a46b8c438cee7",
"static/images/docs/oauth-server/dark/oauth2-server-discovery.png": "e688439f87c4ae73086b76ee094e036d5bddb14b9431aaad5901d281ab3ca413",
"static/images/docs/oauth-server/dark/oauth2-server-secret-created.png": "d675fe574706d58b224ce823c8904145482472f64ab940a8bb37b92fcc33008e",
"static/images/docs/oauth-server/dark/oauth2-server-settings-disabled.png": "ad51d531eaa3736839859097c024bca41a28d1339ceef773e376c06936e040ee",
"static/images/docs/oauth-server/dark/oauth2-server-settings.png": "18da0340416739f803b8c04d66c191e92e7eafa70cd301acb1e269d8dd5a0c81",
"static/images/docs/oauth-server/dark/oauth2-server-token-lifetimes.png": "46743e2f0a3e8c126d590809668a37c2beb1f1c5483002839d9a15eca9e110d4",
"static/images/docs/oauth-server/dark/scopes-defined.png": "4723658a478b30e4f97372661d4ed7a9ba14d1cc7f0b56d4f5bf6148b5326cf1",
"static/images/docs/oauth-server/diagram-authorization.png": "84371bcc0218c49124772bb1efffd98880195f580dd53350c57c93ac7a0cea94",
"static/images/docs/oauth-server/diagram-clients.png": "d288a77ddede75c405419df9e3477d471ee7cb57ed63efe6c005cdf2b35098ac",
"static/images/docs/oauth-server/diagram-device-flow.png": "2e887ebd32a76f9ba1aae2f377f7e5c74f4456f36c91ee767aeb4a3dc307a7ee",
"static/images/docs/oauth-server/diagram-overview.png": "71488c2c882c2befa2384cf20aaff7f681bf55b52f3247a4b0f3e73b3684f412",
"static/images/docs/oauth-server/diagram-scopes.png": "6bc6b498f71b1ec5ac1fdac3cddedca3749b2e50c8452e587235b366b577638c",
"static/images/docs/oauth-server/diagram-tokens.png": "62de8f079c3b585c312c13c830fc53d957e8fc8e6432eaa0d33c5a1942e331bc",
"static/images/docs/oauth-server/guide/taskflow-consent.png": "5c4f4cbc6b225c60d5d6e2f1616abe45f0e6b66a5014438961fca98db349bd18",
"static/images/docs/oauth-server/guide/taskflow-login.png": "5d0013b31b211efe4d3a2f6a9f224a5b547d3ef1370e507b9a183bb276fc90e1",
"static/images/docs/oauth-server/guide/vantage-dashboard.png": "b2e95c34ec9c19be090e5f3384e0c3278dd840059b3f015ccdb13a16c135dde1",
"static/images/docs/oauth-server/guide/vantage-landing.png": "dc597717bcc93de02b2e5336a0f05d743ea66e6d91cef40fbdc61f8380577bec",
"static/images/docs/oauth-server/oauth2-server-apps-empty.png": "6f7b5c2021df2db7a3a1c4da3dfcf9b2ff119e98450e755eb64f03263030da0a",
"static/images/docs/oauth-server/oauth2-server-apps-list.png": "e3f38509e45e502f742e7532ae1a19bdd35b0891821b6a81601b7960ff16b38c",
"static/images/docs/oauth-server/oauth2-server-create-app.png": "fafb26a8844e4d792a8105c1da2f4c403df117887eeda0fd1c4ea3384929facd",
"static/images/docs/oauth-server/oauth2-server-discovery.png": "5b75f8e780cac92a68fda24a9686ba89ce83d89ac66e5f651428b4c62bb81cbd",
"static/images/docs/oauth-server/oauth2-server-secret-created.png": "5a6d48595018a8faaa26aeafe099c3dda23613a35e49884decbecb0029449168",
"static/images/docs/oauth-server/oauth2-server-settings-disabled.png": "648091d9d81308ab93dd456f172fa1c1977c2fbce9b8aa03893398bdefe10595",
"static/images/docs/oauth-server/oauth2-server-settings.png": "914aa6511caedb568f199eacef837366c523f78125c551afa992e39098707431",
"static/images/docs/oauth-server/oauth2-server-token-lifetimes.png": "895bf62c7f5171c8f244fda37b516760266c47447f2c66c93c3c812139823b96",
"static/images/docs/oauth-server/scopes-defined.png": "ae1d03cc7cd585f52b6445f5b6506a77f3cb47f50f5677c23e5ff650667942b4",
"static/images/docs/oauth-server/scopes-guide/taskflow-consent-choice.png": "27a8ebf167728a50059bf315bc9676e3c5ec9f32f47eb70ea7c4e4715fadbf99",
"static/images/docs/oauth-server/scopes-guide/taskflow-consent-scopes.png": "ad7975be44e1840e85d994c3a12e19bcbe63e4eec5ba4bec9028c6683db3ae60",
"static/images/docs/oauth-server/scopes-guide/vantage-tasks-dashboard.png": "a621e10d803c25408eafcb0393e934080c4a2fd00e3230ad2a8cab0fbc84d778",
"static/images/docs/oauth-server/scopes-guide/vantage-write-denied.png": "7e2f32309bad509c9920e5725f8b49654e4dbc19699ee6af309b174e955bd2fd",
"static/images/docs/oauth-server/scopes-guide/vantage-write-granted.png": "1565538a08f94180a7ff35df2cad426c962609eeeae021e2fc8c076242882e1e",
"static/images/docs/platform/add-platform.png": "5a05bb9d75a8d5270bfa5e67df7e6de20a9fad174476a112b5bdab72e7bdad30",
"static/images/docs/platform/create-api-key.png": "7661b3845e13704643f8ff4f763faa8e61efb90878c3ffa7466ece0910b8ecab",
"static/images/docs/platform/create-webhook.png": "77e08173da6ac534524e025433cf75e532d853a135944a7c6ba2278357d88b2d",
Expand Down
2 changes: 2 additions & 0 deletions src/lib/utils/code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ const languages = {
java: java,
cpp: cpp,
bash: bash,
curl: bash,
hurl: http,
powershell: powershell,
cmd: dos,
yaml: yaml,
Expand Down
2 changes: 2 additions & 0 deletions src/lib/utils/references.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ export const platformMap: Record<Language | string, string> = {
csharp: 'C#',
cpp: 'C++',
bash: 'Bash',
curl: 'cURL',
hurl: 'Hurl',
powershell: 'PowerShell',
cmd: 'CMD',
yaml: 'YAML',
Expand Down
265 changes: 265 additions & 0 deletions src/routes/blog/post/announcing-oauth2-server/+page.markdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
---
layout: post
title: "Announcing the Appwrite OAuth2 server: Turn your project into an identity provider"
description: Your Appwrite project can now act as an OAuth 2.1 and OpenID Connect provider, so other apps can offer Sign in with your product and request scoped access to your APIs.
date: 2026-07-15
cover: /images/blog/announcing-oauth2-server/cover.png
timeToRead: 6
author: matej-baco
category: announcement
featured: false
callToAction: true
draft: true
faqs:
- question: "What is the Appwrite OAuth2 server?"
answer: "The OAuth2 server lets your Appwrite project act as an OAuth 2.1 and OpenID Connect provider. Third-party apps register as clients, send your users through an authorization flow you control, and receive tokens they can use to sign users in or call your APIs with scoped access."
- question: "How is the OAuth2 server different from Appwrite's OAuth providers?"
answer: "OAuth providers let your users sign in to your app with Google, GitHub, and other identity providers. The OAuth2 server is the other direction: your project becomes the identity provider, and other apps integrate with you the same way you would integrate with Google."
- question: "Which OAuth standards does the Appwrite OAuth2 server support?"
answer: "The server implements OAuth 2.1 with PKCE, OpenID Connect Core, discovery metadata, token introspection and revocation, the device authorization flow, pushed authorization requests, dynamic client registration, JWT access tokens, and rich authorization requests."
- question: "Can users register OAuth2 clients without the Appwrite Console?"
answer: "Yes. The apps service is available in the Client SDKs, so any signed-in user on your project can register and manage their own clients. This lets you build a self-serve developer portal on top of your project, and the standard dynamic client registration endpoint is also supported."
- question: "Can I define custom scopes for my own APIs?"
answer: "Yes. Alongside the built-in openid, profile, and email scopes, you can define scopes like tasks.read for your own APIs. Your resource server verifies the access token and checks the granted scopes before serving a request."
- question: "Is the OAuth2 server available on self-hosted Appwrite?"
answer: "The OAuth2 server is available on Appwrite Cloud. Every project includes it, and you can enable it from the Auth section of the Console."
---

If you have ever added **Sign in with Google** or **Sign in with GitHub** to an app, you have been on the consuming side of OAuth. Being on the other side, the side that issues the tokens, has always been much harder. Building an authorization server means implementing authorize and token endpoints, PKCE, consent, token rotation, client management, and a stack of specifications that are easy to get subtly wrong.

That work is now part of your Appwrite project. With the **OAuth2 server**, your project can act as an **OAuth 2.1 and OpenID Connect provider**. Third-party developers register their apps as clients, your users approve what those apps can access, and your project issues the tokens. Integrators can offer **Sign in with your product** and request scoped access to your APIs, using the same standard flows they already know from Google or GitHub.

# What your project provides

Once enabled, your project exposes the full set of endpoints an OAuth or OIDC integration expects: authorization, token, userinfo, introspection, revocation, logout, device authorization, pushed authorization requests, and dynamic client registration. All of them are described by a discovery document, so most integrators point their OAuth library at one URL and never read your endpoint list:

```text
https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/.well-known/openid-configuration
```

Two design decisions matter more than the endpoint list.

First, **you own the consent screen**. The OAuth2 server handles the protocol, but the page where users review and approve a request is a page you host, built with your components and your branding. Users approving access to their data never leave your product's design language.

Second, **client registration is an API, not a Console form**. The `apps` service is available in the Client SDKs, so any signed-in user on your project can register an app. That is what makes a self-serve developer portal possible: integrators create their clients, manage their secrets, and rotate credentials inside your product, while the Console remains your admin view.

# Enable the server in the Console

The OAuth2 server lives in the **Auth** section of the Console, under the **OAuth2 server** tab. Turn it on, set the authorization URL to the page that will host your consent screen, and add any custom scopes your APIs support. The `openid`, `profile`, and `email` scopes are always included.

Once enabled, the Console shows your project's discovery URL, and opening it in a browser confirms the server is live.

![The OIDC discovery URL in the Appwrite Console](/images/docs/oauth-server/dark/oauth2-server-discovery.avif)

# Register a client

The rest of this post follows two apps:

- **TaskFlow**: your product, a task manager built on Appwrite, acting as the OAuth2 provider.
- **Vantage**: a third-party analytics dashboard that wants to offer **Sign in with TaskFlow**.

Vantage registers as a client with a name and a redirect URI, the URL where users return to Vantage with an authorization code after approving access. You can create the client in the **Apps** sub-tab of the Console, or let integrators like Vantage register their own from code:

{% multicode %}
```client-web
import { Client, Apps, ID } from 'appwrite';

const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const app = await apps.create({
appId: ID.unique(),
name: 'Vantage',
redirectUris: ['http://vantage.localhost/auth/redirect'],
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>'); // Your project ID

Apps apps = Apps(client);

App app = await apps.create(
appId: ID.unique(),
name: 'Vantage',
redirectUris: ['http://vantage.localhost/auth/redirect'],
);
```
```client-apple
import Appwrite

let client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID

let apps = Apps(client)

let app = try await apps.create(
appId: ID.unique(),
name: "Vantage",
redirectUris: ["http://vantage.localhost/auth/redirect"]
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.services.Apps

val client = Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID

val apps = Apps(client)

val app = apps.create(
appId = ID.unique(),
name = "Vantage",
redirectUris = listOf("http://vantage.localhost/auth/redirect")
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.ID;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Apps;

Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID

Apps apps = new Apps(client);

apps.create(
ID.unique(), // appId
"Vantage", // name
List.of("http://vantage.localhost/auth/redirect"), // redirectUris
null, // description (optional)
null, // clientUri (optional)
null, // logoUri (optional)
null, // privacyPolicyUrl (optional)
null, // termsUrl (optional)
null, // contacts (optional)
null, // tagline (optional)
null, // tags (optional)
null, // images (optional)
null, // supportUrl (optional)
null, // dataDeletionUrl (optional)
null, // postLogoutRedirectUris (optional)
null, // enabled (optional)
null, // type (optional)
null, // deviceFlow (optional)
null, // teamId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}

Log.d("Appwrite", result.toString());
})
);
```
```client-react-native
import { Client, Apps, ID } from "react-native-appwrite";

const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const app = await apps.create({
appId: ID.unique(),
name: 'Vantage',
redirectUris: ['http://vantage.localhost/auth/redirect'],
});
```
{% /multicode %}

Clients come in two types:

- **Confidential** clients run on a server, hold a client secret, and authenticate the token exchange with it.
- **Public** clients, such as mobile and single-page apps, cannot keep a secret and use PKCE instead.

Beyond the basics, each client carries consent screen branding, post-logout redirect URIs, and an optional device flow, and clients can be transferred to teams so a whole organization manages them together.

# Run a sign-in

The default flow is the standard authorization code flow. When a user clicks **Sign in with TaskFlow**, Vantage sends their browser to TaskFlow's authorization endpoint with its client ID, redirect URI, and requested scopes:

```text
https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/authorize
?client_id=<CLIENT_ID>
&redirect_uri=http://vantage.localhost/auth/redirect
&response_type=code
&scope=openid profile email
&state=<STATE>
```

Appwrite validates the request and redirects the user to TaskFlow's consent screen with a grant ID. The page shows what Vantage is asking for, and when the user approves, Appwrite sends them back to Vantage with an authorization code. Vantage's server then exchanges the code for tokens:

{% multicode %}
```curl
curl -X POST 'https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/token' \
-H 'Content-Type: application/json' \
-d '{
"grant_type": "authorization_code",
"code": "<CODE>",
"redirect_uri": "http://vantage.localhost/auth/redirect",
"client_id": "<CLIENT_ID>",
"client_secret": "<SECRET_FOR_CONFIDENTIAL_CLIENTS>",
"code_verifier": "<PKCE_VERIFIER_FOR_PUBLIC_CLIENTS>"
}'
```
```hurl
POST https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/token
Content-Type: application/json
{
"grant_type": "authorization_code",
"code": "<CODE>",
"redirect_uri": "http://vantage.localhost/auth/redirect",
"client_id": "<CLIENT_ID>",
"client_secret": "<SECRET_FOR_CONFIDENTIAL_CLIENTS>",
"code_verifier": "<PKCE_VERIFIER_FOR_PUBLIC_CLIENTS>"
}
```
{% /multicode %}

The response contains a JWT access token, a refresh token with rotation, and an OIDC ID token. A call to the userinfo endpoint with the access token returns the user's profile, which is everything Vantage needs to sign the user in on its side.

# Protect your own APIs with custom scopes

Sign-in is the starting point. The larger opportunity is scoped access to your APIs. TaskFlow defines scopes like `tasks.read` or `tasks.write` on the server, Vantage requests them during authorization, and users see exactly what they are granting on the consent screen before Vantage can read a single task.

Access tokens are signed JWTs that carry the granted scopes, so your API verifies a request with your project's public keys from the JWKS endpoint and checks the scope before serving it. No call back to Appwrite is needed on the hot path. For fine-grained cases that scopes cannot express, such as an app requesting access to one specific resource, the server also supports rich authorization requests (RFC 9396).

# Standards support

Every flow follows the corresponding specification, so off-the-shelf OAuth and OIDC libraries work without custom adapters:

| Standard | What it covers |
| --- | --- |
| OAuth 2.1 with PKCE | Authorization code flow with proof key for code exchange |
| OpenID Connect Core | ID tokens, userinfo, and RP-initiated logout |
| RFC 8414 | Discovery metadata for automatic client configuration |
| RFC 7662 and RFC 7009 | Token introspection and revocation |
| RFC 8628 | Device authorization flow for TVs and CLIs |
| RFC 9126 | Pushed authorization requests |
| RFC 7591 | Dynamic client registration |
| RFC 9068 | JWT access tokens |
| RFC 9396 | Rich authorization requests |

# Get started

The OAuth2 server is available on Appwrite Cloud. Enable it from the **Auth** section of your project, register a client, and run your first sign-in in a few minutes with the quick start.

- [OAuth2 server documentation](/docs/partners/oauth-server)
- [Quick start: run your first authorization flow](/docs/partners/oauth-server/quick-start)
- [Tutorial: add Sign in with your product to an app](/docs/partners/oauth-server/sign-in-with-your-product/step-1)
- [Tutorial: protect an API with custom scopes](/docs/partners/oauth-server/custom-scopes/step-1)
Loading
Loading