---
layout: article
title: OAuth2 server quick start
description: Enable Appwrite's OAuth2 server, register a client, and run your first authorization code sign-in end to end.
back: /docs/products/auth/oauth-server
---

This guide turns your project into an OAuth2 provider and runs one sign-in through it. By the end you will have an enabled server, a registered client, and an access token issued by your project.

The examples follow two apps, the same pair the [tutorials](/docs/products/auth/oauth-server/sign-in-with-your-product/step-1) build out in full:

- **TaskFlow** (`https://taskflow.localhost`): your product and the **OAuth2 provider**, also called the authorization server. It authenticates users, presents the consent screen, and issues tokens.
- **Vantage** (`https://vantage.localhost`): the third-party **consumer**, called the client in OAuth2. It sends users to TaskFlow for authorization and receives tokens after they approve access.

# Enable the server

In the Console, open **Auth**, select the **OAuth2 server** tab, and turn on **Enable OAuth2 server**.

![Enabling the OAuth2 server in the Appwrite Console](/images/docs/oauth-server/oauth2-server-settings.avif)

Set the **Authorization URL** to the page you will host the consent screen on. This is where Appwrite redirects users during authorization, and where you present them the details of the request so they can approve or reject. Point it at `https://taskflow.localhost/consent`, where TaskFlow will host its consent screen. Nothing needs to run there yet. The `openid`, `profile`, and `email` scopes are always included; add any scopes your custom APIs will support.

You can also enable and configure the server with a [Server SDK](/docs/sdks#server) using the `updateOAuth2Server` method.

**Required scope**

The API key used for this call needs the `project.write` scope.

```server-nodejs
import { Client, Project } from 'node-appwrite';

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

const project = new Project(client);

const result = await project.updateOAuth2Server({
    enabled: true,
    authorizationUrl: 'https://example.com',
    scopes: [], // optional
    authorizationDetailsTypes: [], // optional
    accessTokenDuration: 60, // optional
    refreshTokenDuration: 60, // optional
    publicAccessTokenDuration: 60, // optional
    publicRefreshTokenDuration: 60, // optional
    confidentialPkce: false, // optional
    verificationUrl: 'https://example.com', // optional
    userCodeLength: 6, // optional
    userCodeFormat: 'numeric', // optional
    deviceCodeDuration: 60 // optional
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

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

const project = new Project(client);

const result = await project.updateOAuth2Server({
    enabled: true,
    authorizationUrl: 'https://example.com',
    scopes: [], // optional
    authorizationDetailsTypes: [], // optional
    accessTokenDuration: 60, // optional
    refreshTokenDuration: 60, // optional
    publicAccessTokenDuration: 60, // optional
    publicRefreshTokenDuration: 60, // optional
    confidentialPkce: false, // optional
    verificationUrl: 'https://example.com', // optional
    userCodeLength: 6, // optional
    userCodeFormat: 'numeric', // optional
    deviceCodeDuration: 60 // optional
});
```
```server-php
```php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    ->setProject('<YOUR_PROJECT_ID>') // Your project ID
    ->setKey('<YOUR_API_KEY>'); // Your secret API key

$project = new Project($client);

$result = $project->updateOAuth2Server(
    enabled: true,
    authorizationUrl: 'https://example.com',
    scopes: [], // optional
    authorizationDetailsTypes: [], // optional
    accessTokenDuration: 60, // optional
    refreshTokenDuration: 60, // optional
    publicAccessTokenDuration: 60, // optional
    publicRefreshTokenDuration: 60, // optional
    confidentialPkce: false, // optional
    verificationUrl: 'https://example.com', // optional
    userCodeLength: 6, // optional
    userCodeFormat: 'numeric', // optional
    deviceCodeDuration: 60 // optional
);```
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project
from appwrite.models import Project as ProjectModel

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
client.set_project('<YOUR_PROJECT_ID>') # Your project ID
client.set_key('<YOUR_API_KEY>') # Your secret API key

project = Project(client)

result: ProjectModel = project.update_o_auth2_server(
    enabled = True,
    authorization_url = 'https://example.com',
    scopes = [], # optional
    authorization_details_types = [], # optional
    access_token_duration = 60, # optional
    refresh_token_duration = 60, # optional
    public_access_token_duration = 60, # optional
    public_refresh_token_duration = 60, # optional
    confidential_pkce = False, # optional
    verification_url = 'https://example.com', # optional
    user_code_length = 6, # optional
    user_code_format = 'numeric', # optional
    device_code_duration = 60 # optional
)

print(result.model_dump())
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
    .set_project('<YOUR_PROJECT_ID>') # Your project ID
    .set_key('<YOUR_API_KEY>') # Your secret API key

project = Project.new(client)

result = project.update_o_auth2_server(
    enabled: true,
    authorization_url: 'https://example.com',
    scopes: [], # optional
    authorization_details_types: [], # optional
    access_token_duration: 60, # optional
    refresh_token_duration: 60, # optional
    public_access_token_duration: 60, # optional
    public_refresh_token_duration: 60, # optional
    confidential_pkce: false, # optional
    verification_url: 'https://example.com', # optional
    user_code_length: 6, # optional
    user_code_format: 'numeric', # optional
    device_code_duration: 60 # optional
)
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

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

Project project = Project(client);

Project result = await project.updateOAuth2Server(
    enabled: true,
    authorizationUrl: 'https://example.com',
    scopes: [], // (optional)
    authorizationDetailsTypes: [], // (optional)
    accessTokenDuration: 60, // (optional)
    refreshTokenDuration: 60, // (optional)
    publicAccessTokenDuration: 60, // (optional)
    publicRefreshTokenDuration: 60, // (optional)
    confidentialPkce: false, // (optional)
    verificationUrl: 'https://example.com', // (optional)
    userCodeLength: 6, // (optional)
    userCodeFormat: 'numeric', // (optional)
    deviceCodeDuration: 60, // (optional)
);
```
```server-dotnet
```csharp
using Appwrite;
using Appwrite.Models;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .SetProject("<YOUR_PROJECT_ID>") // Your project ID
    .SetKey("<YOUR_API_KEY>"); // Your secret API key

Project project = new Project(client);

Project result = await project.UpdateOAuth2Server(
    enabled: true,
    authorizationUrl: "https://example.com",
    scopes: new List<string>(), // optional
    authorizationDetailsTypes: new List<string>(), // optional
    accessTokenDuration: 60, // optional
    refreshTokenDuration: 60, // optional
    publicAccessTokenDuration: 60, // optional
    publicRefreshTokenDuration: 60, // optional
    confidentialPkce: false, // optional
    verificationUrl: "https://example.com", // optional
    userCodeLength: 6, // optional
    userCodeFormat: "numeric", // optional
    deviceCodeDuration: 60 // optional
);```
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Project

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

val project = Project(client)

val response = project.updateOAuth2Server(
    enabled = true,
    authorizationUrl = "https://example.com",
    scopes = listOf(), // optional
    authorizationDetailsTypes = listOf(), // optional
    accessTokenDuration = 60, // optional
    refreshTokenDuration = 60, // optional
    publicAccessTokenDuration = 60, // optional
    publicRefreshTokenDuration = 60, // optional
    confidentialPkce = false, // optional
    verificationUrl = "https://example.com", // optional
    userCodeLength = 6, // optional
    userCodeFormat = "numeric", // optional
    deviceCodeDuration = 60 // optional
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

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

Project project = new Project(client);

project.updateOAuth2Server(
    true, // enabled
    "https://example.com", // authorizationUrl
    List.of(), // scopes (optional)
    List.of(), // authorizationDetailsTypes (optional)
    60, // accessTokenDuration (optional)
    60, // refreshTokenDuration (optional)
    60, // publicAccessTokenDuration (optional)
    60, // publicRefreshTokenDuration (optional)
    false, // confidentialPkce (optional)
    "https://example.com", // verificationUrl (optional)
    6, // userCodeLength (optional)
    "numeric", // userCodeFormat (optional)
    60, // deviceCodeDuration (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

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

let project = Project(client)

let project = try await project.updateOAuth2Server(
    enabled: true,
    authorizationUrl: "https://example.com",
    scopes: [], // optional
    authorizationDetailsTypes: [], // optional
    accessTokenDuration: 60, // optional
    refreshTokenDuration: 60, // optional
    publicAccessTokenDuration: 60, // optional
    publicRefreshTokenDuration: 60, // optional
    confidentialPkce: false, // optional
    verificationUrl: "https://example.com", // optional
    userCodeLength: 6, // optional
    userCodeFormat: "numeric", // optional
    deviceCodeDuration: 60 // optional
)
```
```server-go
package main

import (
    "fmt"
    "github.com/repoowner/reponame/client"
    "github.com/repoowner/reponame/project"
)

client := client.New(
    client.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    client.WithProject("<YOUR_PROJECT_ID>")
    client.WithKey("<YOUR_API_KEY>")
)

service := project.New(client)

response, error := service.UpdateOAuth2Server(
    true,
    "https://example.com",
    project.WithUpdateOAuth2ServerScopes([]string{}),
    project.WithUpdateOAuth2ServerAuthorizationDetailsTypes([]string{}),
    project.WithUpdateOAuth2ServerAccessTokenDuration(60),
    project.WithUpdateOAuth2ServerRefreshTokenDuration(60),
    project.WithUpdateOAuth2ServerPublicAccessTokenDuration(60),
    project.WithUpdateOAuth2ServerPublicRefreshTokenDuration(60),
    project.WithUpdateOAuth2ServerConfidentialPkce(false),
    project.WithUpdateOAuth2ServerVerificationUrl("https://example.com"),
    project.WithUpdateOAuth2ServerUserCodeLength(6),
    project.WithUpdateOAuth2ServerUserCodeFormat("numeric"),
    project.WithUpdateOAuth2ServerDeviceCodeDuration(60),
)
```
```server-rust
use appwrite::Client;
use appwrite::services::Project;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint
    client.set_project("<YOUR_PROJECT_ID>"); // Your project ID
    client.set_key("<YOUR_API_KEY>"); // Your secret API key

    let project = Project::new(&client);

    let result = project.update_o_auth2_server(
        true,
        "https://example.com",
        Some(vec![]), // optional
        Some(vec![]), // optional
        Some(60), // optional
        Some(60), // optional
        Some(60), // optional
        Some(60), // optional
        Some(false), // optional
        Some("https://example.com"), // optional
        Some(6), // optional
        Some("numeric"), // optional
        Some(60) // optional
    ).await?;

    let _ = result;

    Ok(())
}
```

# Copy the discovery URL

Once enabled, the server publishes an OpenID Connect discovery document. Integrators point their OAuth or OIDC library at this URL and it learns every endpoint automatically. If the integrating platform supports OIDC sign-in out of the box, this URL is all it needs: it can skip most of the steps below instead of building the flow from scratch.

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

Open it in a browser to confirm the server is live. It returns JSON describing the authorization, token, userinfo, and JWKS endpoints, and more.

# Register a client

Each app that integrates with your project registers as a client. Here that is Vantage. The **redirect URI** it declares is a URL on Vantage, `https://vantage.localhost/auth/redirect`, where the OAuth2 server sends users back with the authorization code. For security, redirect URIs must use HTTPS. Loopback addresses like `localhost`, `127.0.0.1`, and `[::1]` are the exception and can use HTTP during development.

Open the **Apps** sub-tab and create a client. Name it `Vantage`, add the redirect URI, and choose a type. Pick **Confidential** for this walkthrough so you get a secret to authenticate the token exchange. Copy the secret when it is shown, because it appears only once. For a mobile or single-page app that cannot hold a secret, pick **Public** instead: the token exchange then uses [PKCE](/docs/products/auth/oauth-server/authorization#pkce) in place of the secret, and the [client types](/docs/products/auth/oauth-server/clients#client-types) comparison shows what else changes.

![Creating an OAuth2 client](/images/docs/oauth-server/oauth2-server-create-app.avif)

Clients can also be registered from code with the `apps` service in the [Client SDKs](/docs/sdks#client). Any signed-in user on your project can register an app, which enables self-serve registration for integrators. Creating a client needs only a name and a redirect URI:

```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: ['https://vantage.localhost/auth/redirect'],
    type: 'confidential',
});
```
```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: ['https://vantage.localhost/auth/redirect'],
    type: 'confidential',
);
```
```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: ["https://vantage.localhost/auth/redirect"],
    type: "confidential"
)
```
```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("https://vantage.localhost/auth/redirect"),
    type = "confidential"
)
```
```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("https://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)
    "confidential", // 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: ['https://vantage.localhost/auth/redirect'],
    type: 'confidential',
});
```

Updating a client accepts the full set of options, from consent screen branding to logout URIs and the device flow:

```client-web
import { Client, Apps } 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.update({
    appId: '<APP_ID>',
    name: 'Vantage',
    description: 'A dashboard that signs in with TaskFlow.',
    clientUri: 'https://vantage.localhost',
    logoUri: 'https://vantage.localhost/logo.png',
    privacyPolicyUrl: 'https://vantage.localhost/privacy',
    termsUrl: 'https://vantage.localhost/terms',
    contacts: ['security@vantage.localhost'],
    tagline: 'Product analytics for modern teams',
    tags: ['analytics', 'productivity'],
    images: ['https://vantage.localhost/screenshot.png'],
    supportUrl: 'https://vantage.localhost/support',
    dataDeletionUrl: 'https://vantage.localhost/data-deletion',
    enabled: true,
    redirectUris: ['https://vantage.localhost/auth/redirect'],
    postLogoutRedirectUris: ['https://vantage.localhost/signed-out'],
    type: 'confidential',
    deviceFlow: false,
});
```
```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.update(
    appId: '<APP_ID>',
    name: 'Vantage',
    description: 'A dashboard that signs in with TaskFlow.',
    clientUri: 'https://vantage.localhost',
    logoUri: 'https://vantage.localhost/logo.png',
    privacyPolicyUrl: 'https://vantage.localhost/privacy',
    termsUrl: 'https://vantage.localhost/terms',
    contacts: ['security@vantage.localhost'],
    tagline: 'Product analytics for modern teams',
    tags: ['analytics', 'productivity'],
    images: ['https://vantage.localhost/screenshot.png'],
    supportUrl: 'https://vantage.localhost/support',
    dataDeletionUrl: 'https://vantage.localhost/data-deletion',
    enabled: true,
    redirectUris: ['https://vantage.localhost/auth/redirect'],
    postLogoutRedirectUris: ['https://vantage.localhost/signed-out'],
    type: 'confidential',
    deviceFlow: false,
);
```
```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.update(
    appId: "<APP_ID>",
    name: "Vantage",
    description: "A dashboard that signs in with TaskFlow.",
    clientUri: "https://vantage.localhost",
    logoUri: "https://vantage.localhost/logo.png",
    privacyPolicyUrl: "https://vantage.localhost/privacy",
    termsUrl: "https://vantage.localhost/terms",
    contacts: ["security@vantage.localhost"],
    tagline: "Product analytics for modern teams",
    tags: ["analytics", "productivity"],
    images: ["https://vantage.localhost/screenshot.png"],
    supportUrl: "https://vantage.localhost/support",
    dataDeletionUrl: "https://vantage.localhost/data-deletion",
    enabled: true,
    redirectUris: ["https://vantage.localhost/auth/redirect"],
    postLogoutRedirectUris: ["https://vantage.localhost/signed-out"],
    type: "confidential",
    deviceFlow: false
)
```
```client-android-kotlin
import io.appwrite.Client
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.update(
    appId = "<APP_ID>",
    name = "Vantage",
    description = "A dashboard that signs in with TaskFlow.",
    clientUri = "https://vantage.localhost",
    logoUri = "https://vantage.localhost/logo.png",
    privacyPolicyUrl = "https://vantage.localhost/privacy",
    termsUrl = "https://vantage.localhost/terms",
    contacts = listOf("security@vantage.localhost"),
    tagline = "Product analytics for modern teams",
    tags = listOf("analytics", "productivity"),
    images = listOf("https://vantage.localhost/screenshot.png"),
    supportUrl = "https://vantage.localhost/support",
    dataDeletionUrl = "https://vantage.localhost/data-deletion",
    enabled = true,
    redirectUris = listOf("https://vantage.localhost/auth/redirect"),
    postLogoutRedirectUris = listOf("https://vantage.localhost/signed-out"),
    type = "confidential",
    deviceFlow = false,
)
```
```client-android-java
import io.appwrite.Client;
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.update(
    "<APP_ID>", // appId
    "Vantage", // name
    "A dashboard that signs in with TaskFlow.", // description
    "https://vantage.localhost", // clientUri
    "https://vantage.localhost/logo.png", // logoUri
    "https://vantage.localhost/privacy", // privacyPolicyUrl
    "https://vantage.localhost/terms", // termsUrl
    List.of("security@vantage.localhost"), // contacts
    "Product analytics for modern teams", // tagline
    List.of("analytics", "productivity"), // tags
    List.of("https://vantage.localhost/screenshot.png"), // images
    "https://vantage.localhost/support", // supportUrl
    "https://vantage.localhost/data-deletion", // dataDeletionUrl
    true, // enabled
    List.of("https://vantage.localhost/auth/redirect"), // redirectUris
    List.of("https://vantage.localhost/signed-out"), // postLogoutRedirectUris
    "confidential", // type
    false, // deviceFlow
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Apps } 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.update({
    appId: '<APP_ID>',
    name: 'Vantage',
    description: 'A dashboard that signs in with TaskFlow.',
    clientUri: 'https://vantage.localhost',
    logoUri: 'https://vantage.localhost/logo.png',
    privacyPolicyUrl: 'https://vantage.localhost/privacy',
    termsUrl: 'https://vantage.localhost/terms',
    contacts: ['security@vantage.localhost'],
    tagline: 'Product analytics for modern teams',
    tags: ['analytics', 'productivity'],
    images: ['https://vantage.localhost/screenshot.png'],
    supportUrl: 'https://vantage.localhost/support',
    dataDeletionUrl: 'https://vantage.localhost/data-deletion',
    enabled: true,
    redirectUris: ['https://vantage.localhost/auth/redirect'],
    postLogoutRedirectUris: ['https://vantage.localhost/signed-out'],
    type: 'confidential',
    deviceFlow: false,
});
```

The same `apps` service is available in the [Server SDKs](/docs/sdks#server) with an API key. See [Clients](/docs/products/auth/oauth-server/clients) for the full set of options.

# Run the authorization code flow

With the server enabled and a client registered, you can run a sign-in. The flow has four steps: send the user to authorize, approve the grant, exchange the returned code for tokens, and use the access token to read their profile.

## 1. Send the user to the authorization endpoint

Vantage begins the sign-in by sending the user's browser to TaskFlow's authorization endpoint. This is the URL behind Vantage's **Sign in with TaskFlow** button.

The URL uses TaskFlow's Appwrite API endpoint because its Appwrite project is acting as the authorization server. Replace `<REGION>` with the region from your API endpoint, `<PROJECT_ID>` with TaskFlow's Appwrite project ID, and `<CLIENT_ID>` with the ID generated when you registered Vantage:

```text
https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/authorize
    ?client_id=<CLIENT_ID>
    &redirect_uri=https://vantage.localhost/auth/redirect
    &response_type=code
    &scope=openid profile email
```
```curl
curl -G 'https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/authorize' \
  --data-urlencode 'client_id=<CLIENT_ID>' \
  --data-urlencode 'redirect_uri=https://vantage.localhost/auth/redirect' \
  --data-urlencode 'response_type=code' \
  --data-urlencode 'scope=openid profile email'
```
```hurl
GET https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/authorize
[Query]
client_id: <CLIENT_ID>
redirect_uri: https://vantage.localhost/auth/redirect
response_type: code
scope: openid profile email
```

Each query parameter tells TaskFlow how to handle the request:

| Parameter | Meaning |
| --- | --- |
| `client_id` | Identifies Vantage as the client requesting access. |
| `redirect_uri` | Tells TaskFlow where to return the browser after the user approves or rejects. It must match a URI registered for Vantage. |
| `response_type=code` | Requests an authorization code. The browser receives this temporary code, then Vantage's server exchanges it for tokens in step 3. |
| `scope` | Lists the access Vantage is requesting. `openid` starts an OpenID Connect sign-in, while `profile` and `email` request the user's basic profile and email claims. |

When you open this URL, Appwrite validates the request and checks for an active TaskFlow user session.

## 2. Review and approve Vantage's request

When Vantage opens the authorization endpoint for a signed-in user, Appwrite creates a pending authorization request called a **grant**. The grant connects the user, Vantage, the requested scopes, and Vantage's redirect URI. Appwrite then redirects the browser to TaskFlow's **authorization URL**, the consent page, with the `grant_id` in the query string. A complete consent screen uses that grant ID to show what Vantage is asking for and lets the user approve or reject; the [Authorization](/docs/products/auth/oauth-server/authorization#consent) guide shows how to build it.

This quick start does not build TaskFlow's consent screen, so the browser lands on `https://taskflow.localhost/consent` and finds nothing there. That is fine: copy the `grant_id` from the address bar and approve the request manually. If the URL has no `grant_id`, the user has no active TaskFlow session. Sign in a user on the project, then open Vantage's authorization URL again.

Approving needs the same TaskFlow user's session, sent as the `a_session_<PROJECT_ID>` cookie, which holds the session secret. To find its value, open your browser's developer tools on the Network tab, look at any request to Appwrite, such as `GET /v1/account`, and copy the cookie from the request headers. Then send this request:

```curl
curl -X POST 'https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/approve' \
  -H 'Content-Type: application/json' \
  -H 'Cookie: a_session_<PROJECT_ID>=<SESSION_SECRET>' \
  -d '{
    "grant_id": "<GRANT_ID>"
  }'
```
```hurl
POST https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/approve
Content-Type: application/json
Cookie: a_session_<PROJECT_ID>=<SESSION_SECRET>
{
    "grant_id": "<GRANT_ID>"
}
```

After approval, Appwrite redirects the browser to Vantage's registered redirect URI with an authorization `code` in the query string. Vantage uses this code in the next step.

## 3. Exchange the code for tokens

Vantage's server sends the code to the token endpoint. A confidential client passes its `client_secret`; a public client passes its PKCE `code_verifier` instead.

```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": "https://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": "https://vantage.localhost/auth/redirect",
    "client_id": "<CLIENT_ID>",
    "client_secret": "<SECRET_FOR_CONFIDENTIAL_CLIENTS>",
    "code_verifier": "<PKCE_VERIFIER_FOR_PUBLIC_CLIENTS>"
}
```

The response contains an access token, a refresh token, and an ID token:

```json
{
    "access_token": "eyJ0eXAiOiJhdCtqd3Qi...",
    "token_type": "Bearer",
    "expires_in": 28800,
    "refresh_token": "eyJ0eXAiOiJKV1Qi...",
    "scope": "openid profile email",
    "id_token": "eyJ0eXAiOiJKV1Qi..."
}
```

## 4. Read the user's profile

Call the userinfo endpoint with the access token to confirm the sign-in worked end to end:

```curl
curl 'https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/userinfo' \
  -H 'Authorization: Bearer <ACCESS_TOKEN>'
```
```hurl
GET https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/userinfo
Authorization: Bearer <ACCESS_TOKEN>
```

```json
{
    "sub": "6a5138d1971d49a87f0a",
    "email": "walter@example.com",
    "email_verified": true,
    "name": "Walter O'Brien",
    "updated_at": 1784040398
}
```

See [Tokens](/docs/products/auth/oauth-server/tokens) for token structure, validation, refresh, and revocation.

# Next steps

Enabling the server is half of becoming a provider. Integrators like Vantage rely on TaskFlow for two more things:

- **Documentation** that covers the discovery URL, the available scopes and what they grant, and how to register a client.
- **A developer platform** on TaskFlow's own website where integrators register and manage their clients with the `apps` service, which the [Clients](/docs/products/auth/oauth-server/clients) page walks through.

- [Authorization](/docs/products/auth/oauth-server/authorization): Host your consent screen and drive the authorize, grant, and approve steps.
- [Clients](/docs/products/auth/oauth-server/clients): Confidential vs public clients, secrets, and rotation.
- [Tokens](/docs/products/auth/oauth-server/tokens): Token lifetimes, refresh with rotation, introspection, and revocation.
