On This Page

OAuth

15 symbols from @neontechspace/fluxerly

Variables#

OAuthScopes#

Choose which account data the user may share, or request bot installation, in authorizationUrl

const OAuthScopes: Readonly<{ … }>
Bot#

Include bot installation in the consent request

"bot"
Connections#

Read the consenting account's connected accounts

"connections"
Email#

Request the consenting account's email fields

"email"
Guilds#

Read the consenting account's community memberships

"guilds"
Identify#

Read the consenting account's basic identity

"identify"

oauth#

Create confidential OAuth clients and generate PKCE values for authorization-code flows

Use these helpers on a trusted server where the client secret is not exposed to a browser

const oauth: Readonly<{ … }>

The application handles browser navigation, callbacks, state correlation, consent and token storage. It also decides installation policy and coordinates refreshes. The create method returns the client synchronously without making a request, and throws ConfigurationError for invalid settings. The createPkce method returns a new verifier and S256 challenge for the same authorization flow

PKCE links authorization to the code exchange using a private random verifier and its public SHA-256 hash challenge. Keep the verifier private and send only the challenge to authorizationUrl

import { oauth, OAuthScopes } from "@neontechspace/fluxerly"

export async function oauthExample(clientId, clientSecret, redirectUri, state) {
    const client = oauth.create({ clientId, clientSecret })
    try {
        const pkce = oauth.createPkce()
        const authorization = await client.authorizationUrl({
            redirectUri,
            scopes: [OAuthScopes.Identify, OAuthScopes.Bot],
            state,
            codeChallenge: pkce.challenge,
            guildId: "456",
            permissions: 0n,
        })
        // Store the verifier with the matching state in a one-use server record before redirecting the browser.
        // Use it to exchange the callback code, and keep it out of browser responses and logs
        return authorization.map((url) => ({ url, codeVerifier: pkce.verifier }))
    }
    finally {
        await client.shutdown()
    }
}
import { oauth, OAuthScopes } from "@neontechspace/fluxerly"

export async function oauthExample(clientId: string, clientSecret: string, redirectUri: string, state: string) {
    const client = oauth.create({ clientId, clientSecret })
    try {
        const pkce = oauth.createPkce()
        const authorization = await client.authorizationUrl({
            redirectUri,
            scopes: [OAuthScopes.Identify, OAuthScopes.Bot],
            state,
            codeChallenge: pkce.challenge,
            guildId: "456",
            permissions: 0n,
        })
        // Store the verifier with the matching state in a one-use server record before redirecting the browser.
        // Use it to exchange the callback code, and keep it out of browser responses and logs
        return authorization.map((url) => ({ url, codeVerifier: pkce.verifier }))
    } finally {
        await client.shutdown()
    }
}
createPkce#

Generate a private verifier and matching S256 challenge for one authorization-code flow

Send challenge to authorizationUrl and keep verifier privately for exchangeCode. This synchronous helper returns the values immediately, without a request or token storage. It does not generate or verify the callback's state value

() => OAuthPkce

create#

Create a standalone OAuth client with a clientId and server-held clientSecret

Creation checks configuration synchronously without requests and copies the secret until shutdown. Invalid settings throw ConfigurationError. Unexpected creation failures throw SdkDefect with operation oauth.create and without configuration details. A throwing configuration getter uses code application.defect with the thrown value as its cause

create(config: OAuthConfig): OAuthClientcreate(
  config: OAuthConfig
): OAuthClient

Parameters

Interfaces#

OAuthActiveIntrospection#

Token-check result when Fluxer reports this application's access or refresh token as active

Inspect tokenType before using optional access-token expiry data

interface OAuthActiveIntrospection
active#

Fluxer reported this token as active at the time of the request, not guaranteed for later use

true
clientId#

Decimal ID of the application to which the token belongs

string
expiresAtUnixSeconds optional#

Provider-reported Unix seconds for an access token

Refresh-token results omit this field. Fluxer names it exp

number
issuedAtUnixSeconds#

Provider-reported Unix seconds when this token was issued

Fluxer names it iat

number
scopes#

Granted scopes parsed from Fluxer's space-separated response, with no authorization decision or retention

readonly string[]
subjectId optional#

Subject account ID when Fluxer includes one

Its absence does not make an otherwise active access token inactive. Fluxer names it sub

string
tokenType#

Whether the inspected credential is an access token or refresh token

"Bearer" | "refresh_token"

OAuthAuthorization#

Frozen description of the authorization behind a delegated access token, returned by fetchAuthorization

Fluxer reads it from the token alone, so any scope works, and it includes the user only when the token has the identify scope

interface OAuthAuthorization
application#

The application the user authorized

botPublic

Whether anyone can add the application's bot to a community

boolean
botRequireCodeGrant

Whether adding the bot requires the full authorization-code flow

boolean
description

Description, or null

Fluxer currently always sends null here

string | null
flags

Application flags as a 32-bit integer

Fluxer currently always sends 0 here

number
icon

Icon hash, or null

Fluxer currently always sends null here

string | null
id

Decimal application ID

string
name

Application name

string
expiresAt#

When the access token expires, as an ISO 8601 timestamp

Fluxer names it expires

string
scopes#

Scopes granted to the token, as Fluxer lists them

readonly string[]
user optional#

The authorizing user, present only when the token has the identify scope

Email fields also need the email scope

OAuthIdentity

OAuthAuthorizationInput#

Build the consent URL for an authorization-code request with PKCE

The application keeps state and the PKCE verifier. When the callback arrives, it checks that state matches the sent value. The SDK does not save these values, open a browser or handle the redirect. Values are sampled once when authorizationUrl starts, then validated and encoded from that snapshot

interface OAuthAuthorizationInput
channelId optional#

Optional group-DM installation target for the bot scope

Mutually exclusive with guildId and not proof of installation

string
codeChallenge#

Public hash challenge produced by oauth.createPkce, using S256 and exactly 43 base64url characters

Retain the paired secret verifier for exchangeCode when the callback arrives

string
disableGuildSelect optional#

Ask the bot-installation consent page to hide community selection

This does not establish a selected target

boolean
guildId optional#

Optional community installation target for the bot scope

This hint neither proves membership nor grants permissions

string
permissions optional#

Optional unsigned 64-bit bot permission bitfield, serialized as a decimal provider parameter without local authorization

bigint
redirectUri#

A redirect URI registered for the application, using HTTPS, or HTTP for a loopback host or an IP address such as a LAN address, without embedded credentials or a fragment

The SDK applies Fluxer's normalization: It removes U+000C and U+202E and trims surrounding whitespace. The result must contain 1–256 well-formed UTF-16 units and match a registered redirect URI exactly. Fluxer keeps the URL's standard form with the code, such as https://example.test/ for https://example.test, so that form must also fit 256 units

string
scopes#

Provide 1–256 delegated or bot-installation scopes from OAuthScopes

Supplied entries, including duplicates, count toward this SDK defensive limit. Duplicates are removed in first-occurrence order before serialization

readonly OAuthScope[]
state#

Unpredictable caller-generated value of 1–256 well-formed UTF-16 units, verified by the application on callback

Surrounding whitespace, U+000C and U+202E are rejected, not removed, so accepted state is preserved exactly

string

OAuthClient#

Use a standalone OAuth client to exchange authorization codes, refresh or revoke tokens, and read delegated user data

This confidential client requires a server-held client secret, not a bot token. It does not open a browser, handle callbacks, compare state, store tokens or run a gateway. The application owns those steps and coordinates refreshes

interface OAuthClient extends AsyncDisposableinterface OAuthClient
  extends AsyncDisposable

The fetchGuilds and fetchConnections methods read the user's communities and connections. Reading one delegated community, its roles or the fuller current-user profile needs a server-side request with the access token. The client.rest.request method always sends the bot token. The Webhooks & OAuth guide's section Reading with a user token shows the request

The secret is copied until shutdown. Calls start immediately and return ResultAsync. At most eight operations run at once. While eight are running, another operation fails at once with OAuthOperationError reason busy instead of waiting. The timeoutMs option defaults to 30,000 for discovery and the operation, with request cleanup awaited afterward

Malformed options fail with a local input error before discovery. Responses are capped at 1 MiB and requests are never retried automatically. Discovery throttling returns OAuthOperationError with reason rateLimit, outcome notDispatched, status 429 and the server's retryAfterMs when available

Expected failures return Err. A throw while reading input or option properties rejects with SdkDefect code application.defect and the thrown value as its cause, and unexpected cleanup failures reject with SdkDefect. It keeps the original cleanup failure, never response text, and its message and toJSON mask credential patterns

[asyncDispose]#

Shut down this client and wait for cleanup, as await using does at the end of a block

Unexpected cleanup failures reject with SdkDefect, as shutdown does

[asyncDispose](): Promise<void>
Returns
Promise<void>

authorizationUrl#

Build an authorization URL for the selected instance using its discovered web application URL, including its path

Supply the redirect URI, scopes, caller-generated state and S256 PKCE challenge, which binds the callback code to the private verifier. The application must retain state correlation and the PKCE verifier. Bot community and permission parameters are consent hints, not proof of installation or authorization. This returns the URL without opening it. Fluxer's prompt parameter is not supported, so the URL always shows the consent page. Network failures retain only sanitized transport facts in cause

authorizationUrl(
    input: OAuthAuthorizationInput,
    options?: RequestOptions
): ResultAsync<
    string,
    ConfigurationError | OAuthOperationFailure | CancelledError
>authorizationUrl(
  input: OAuthAuthorizationInput,
  options?: RequestOptions
): ResultAsync<
  string,
  | ConfigurationError
  | OAuthOperationFailure
  | CancelledError
>

Parameters

options optional
RequestOptions

exchangeCode#

Exchange the code from an authorization callback for access and refresh tokens

Supply the matching redirect URI and PKCE verifier. The application must store the returned tokens. After dispatch, cancellation or a lost response cannot tell whether Fluxer consumed the one-use code. Do not retry that exchange when its outcome is unknown. Network failures retain only sanitized transport facts in cause

exchangeCode(
    input: OAuthCodeExchangeInput,
    options?: RequestOptions
): ResultAsync<
    OAuthTokens,
    ConfigurationError | OAuthOperationFailure | CancelledError
>exchangeCode(
  input: OAuthCodeExchangeInput,
  options?: RequestOptions
): ResultAsync<
  OAuthTokens,
  | ConfigurationError
  | OAuthOperationFailure
  | CancelledError
>

Parameters

options optional
RequestOptions

fetchAuthorization#

Fetch what the delegated user authorized with an access token: The application, the granted scopes, when the token expires and, with the identify scope, the user

Any valid access token works, so this checks a stored token without the client secret that introspect needs. Fluxer answers a rejected, expired or revoked token with reason rejected and HTTP 401. Failures identify a read in details.read for errors.isRetryable. Requests are not retried automatically, and network causes retain only sanitized transport facts

fetchAuthorization(
    accessToken: string,
    options?: RequestOptions
): ResultAsync<
    OAuthAuthorization,
    ConfigurationError | OAuthOperationFailure | CancelledError
>fetchAuthorization(
  accessToken: string,
  options?: RequestOptions
): ResultAsync<
  OAuthAuthorization,
  | ConfigurationError
  | OAuthOperationFailure
  | CancelledError
>

Parameters

accessToken
string
options optional
RequestOptions

fetchConnections#

Fetch the delegated user's full connections list with a connections-scoped access token

The client neither creates, verifies, reorders nor retains those connections. Failures identify a read in details.read for errors.isRetryable. Requests are not retried automatically, and network causes retain only sanitized transport facts

fetchConnections(
    accessToken: string,
    options?: RequestOptions
): ResultAsync<
    readonly OAuthConnection[],
    ConfigurationError | OAuthOperationFailure | CancelledError
>fetchConnections(
  accessToken: string,
  options?: RequestOptions
): ResultAsync<
  readonly OAuthConnection[],
  | ConfigurationError
  | OAuthOperationFailure
  | CancelledError
>

Parameters

accessToken
string
options optional
RequestOptions

fetchGuilds#

Fetch one bounded page of the delegated user's community memberships

Use an access token with Fluxer's guilds scope. Optionally supply pagination settings in GuildListQuery, where limit defaults to 200 and accepts 1–200, and before and after are mutually exclusive cursors. This uses bearer authentication with the supplied access token, never the bot token. Failures identify a read in details.read for errors.isRetryable. Requests are not retried automatically, and network causes retain only sanitized transport facts

fetchGuilds(
    accessToken: string,
    query?: GuildListQuery,
    options?: RequestOptions
): ResultAsync<
    readonly GuildListSummary[],
    ConfigurationError | OAuthOperationFailure | CancelledError
>fetchGuilds(
  accessToken: string,
  query?: GuildListQuery,
  options?: RequestOptions
): ResultAsync<
  readonly GuildListSummary[],
  | ConfigurationError
  | OAuthOperationFailure
  | CancelledError
>

Parameters

accessToken
string
query optional
GuildListQuery
options optional
RequestOptions

fetchIdentity#

Fetch the delegated user's identity with an access token granted the identify scope

Additional identity fields depend on the token's scopes. The access token is not retained by this client. Failures identify a read in details.read, so errors.isRetryable can classify transient failures without implying a token change. Requests are not retried automatically, and network causes retain only sanitized transport facts

fetchIdentity(
    accessToken: string,
    options?: RequestOptions
): ResultAsync<
    OAuthIdentity,
    ConfigurationError | OAuthOperationFailure | CancelledError
>fetchIdentity(
  accessToken: string,
  options?: RequestOptions
): ResultAsync<
  OAuthIdentity,
  | ConfigurationError
  | OAuthOperationFailure
  | CancelledError
>

Parameters

accessToken
string
options optional
RequestOptions

introspect#

Inspect an access or refresh token using this client's ID and secret through HTTP Basic authentication

An inactive result does not explain expiry or revocation, prove token ownership or establish whether it ever existed. Failures identify a read in details.read for errors.isRetryable. Requests are not retried automatically, and network causes retain only sanitized transport facts

introspect(token: string, options?: RequestOptions): ResultAsync<
    OAuthIntrospection,
    ConfigurationError | OAuthOperationFailure | CancelledError
>introspect(
  token: string,
  options?: RequestOptions
): ResultAsync<
  OAuthIntrospection,
  | ConfigurationError
  | OAuthOperationFailure
  | CancelledError
>

Parameters

token
string
options optional
RequestOptions

refresh#

Exchange a refresh token for a new access and refresh token pair

Form tokens must contain 1–256 well-formed UTF-16 units without surrounding whitespace, U+000C or U+202E. Invalid values fail locally rather than being normalized, and this also applies to revoke and introspect tokens. Fluxer rotates refresh tokens. The application must coordinate refreshes and atomically replace both stored tokens after success, so it never stores a mixed pair

A refresh with an unknown outcome must not be retried, because it may have rotated the token. Network failures retain only sanitized transport facts in cause

refresh(
    refreshToken: string,
    options?: RequestOptions
): ResultAsync<
    OAuthTokens,
    ConfigurationError | OAuthOperationFailure | CancelledError
>refresh(
  refreshToken: string,
  options?: RequestOptions
): ResultAsync<
  OAuthTokens,
  | ConfigurationError
  | OAuthOperationFailure
  | CancelledError
>

Parameters

refreshToken
string
options optional
RequestOptions

revoke#

Revoke the supplied token and every access and refresh token for the same user and application

Other sessions for that user and application stop working too. The token and hint are captured once when execution starts, then validated and transmitted from that snapshot. A lost response can still mean the tokens were revoked. Network failures retain only sanitized transport facts in cause

revoke(
    input: {
        readonly token: string
        readonly tokenTypeHint?: "refresh_token" | "access_token"
    },
    options?: RequestOptions
): ResultAsync<
    void,
    ConfigurationError | OAuthOperationFailure | CancelledError
>revoke(
  input: {
    readonly token: string
    readonly tokenTypeHint?:
      | "refresh_token"
      | "access_token"
  },
  options?: RequestOptions
): ResultAsync<
  void,
  | ConfigurationError
  | OAuthOperationFailure
  | CancelledError
>

Parameters

input
token

Access or refresh token to invalidate

Keep this secret out of logs

string
tokenTypeHint optional

Identify the token as an access token or a refresh token when the kind is known

When omitted, the request asks Fluxer to check refresh tokens first, then access tokens. Fluxer does not find a refresh token sent with the access_token hint

"refresh_token" | "access_token"
options optional
RequestOptions

shutdown#

Permanently stop OAuth work, reject new work, release the client's copied secret and abort active requests

Shutdown waits for fetch and response-reader cleanup. A response-cleanup failure in an active operation fails that operation, while shutdown waits for it and may itself succeed. A failure in shutdown's own discovery cleanup fails shutdown

shutdown(): ResultAsync<void, never>
Returns
ResultAsync<void, never>

Those cleanup failures reject with SdkDefect

OAuthCodeExchangeInput#

Values received or retained by the application for exchangeCode after a consent callback

Verify the callback's state before exchanging its code, since the SDK does not perform that check. Values are sampled once when exchangeCode starts, then validated and transmitted from that snapshot

interface OAuthCodeExchangeInput
code#

Callback code of 1–256 well-formed UTF-16 units to exchange for tokens

Surrounding whitespace, U+000C and U+202E are rejected rather than changing the opaque credential. A dispatched attempt may consume this one-use code even if its response is lost

string
codeVerifier#

Original secret PKCE verifier, such as the verifier returned by oauth.createPkce

Must contain 43–128 ASCII letters, digits or the characters - . _ ~

string
redirectUri#

The redirect URI used in the authorization request, under the same rules

The SDK sends its standard URL form, such as https://example.test/ for https://example.test, because Fluxer compares the exchange with the form it stored for the code

string

OAuthConfig#

Create a server-side OAuth client for exchanging authorization codes with an application secret

Keep the secret on the application server, not in browser code. This client is separate from a bot-token client. The application handles the consent callback and stores tokens

interface OAuthConfig
clientId#

Decimal Fluxer application ID

string
clientSecret#

Application client secret retained until shutdown

The client's logger keeps a copy to mask it while the client object is reachable. Drop the client object to release it

string
instance optional#

Omit for hosted Fluxer

A supplied instance requires its root URL and trusts its discovered service origins

InstanceOptions
logging optional#

Log records for this OAuth client, with the same settings as the client option logging

Omit it for the default console output of Info and above. A rejected client ID or secret logs a rest.rejected Warn record even when the application handles the failure. Repeats follow logging.dedupe, which by default shows one per minute. A rejected user access token is only returned. At Debug, each OAuth request logs one rest.request record with its method, route, status and duration, never its query, form, body or Authorization value

LoggingOptions

OAuthConnection#

One connection returned by fetchConnections with the connections scope, as an immutable value

interface OAuthConnection
id#

Provider connection identifier, which may be empty and is not necessarily a decimal account ID

string
name#

Provider connection name, which may be empty

string
sortOrder#

Provider sort-order value, preserved without reordering the returned list

number
type#

Connection kind accepted by this SDK's response parser, currently bsky or domain

string
verified#

Whether Fluxer reports that the connection is verified

boolean
visibilityFlags#

Provider visibility bitfield, preserved as a nonnegative 32-bit integer without interpreting its flags

number

OAuthIdentity#

Frozen basic account identity returned by fetchIdentity using a delegated access token

Optional email fields are included only when Fluxer supplies them, not synthesized from requested scopes

interface OAuthIdentity
avatar#

Avatar hash, or null when the account has no avatar

string | null
avatarColor optional#

Dominant avatar color, for a fallback while the avatar loads

Null when unset, absent when Fluxer omits it

number | null
discriminator#

Provider discriminator string, preserved without numeric conversion

string
displayName#

Account-wide display name, or null when absent

The display.name helper reads it. Fluxer names it global_name

string | null
email optional#

Email address when supplied, null when explicitly absent, or an omitted field when not returned

string | null
flags optional#

Public account flags, such as badges, absent when Fluxer omits them

Unrecognized 32-bit values are kept

number
id#

Decimal account ID, Fluxer's id field

Fluxer's sub field is not kept, because Fluxer sets it to the same account ID. A GET of /oauth2/userinfo sent with the access token as Bearer authorization returns both. The raw client.rest.request method cannot send it, because it uses the bot token

string
isBot optional#

Whether Fluxer identifies this account as a bot, absent when Fluxer omits it

Fluxer names it bot

boolean
isSystem optional#

Whether Fluxer identifies this account as a system user, absent when Fluxer omits it

Fluxer names it system

boolean
username#

Account username

string
verified optional#

Provider verification flag when supplied, null when unspecified, or an omitted field when not returned

boolean | null

OAuthInactiveIntrospection#

Token-check result when Fluxer does not report the credential as active for this application

This does not distinguish an unknown, expired or revoked token from a token issued to another client

interface OAuthInactiveIntrospection
active#

Fluxer did not report this token as active for this application

false

OAuthPkce#

A PKCE secret and its public hash challenge, used to prove that the code exchange belongs to the authorization request

The application owns these values, and the SDK does not retain them

interface OAuthPkce
challenge#

Public S256 challenge to pass to authorizationUrl

string
verifier#

Secret to retain for exchangeCode, not to publish in the consent URL or logs

string

OAuthTokens#

Credentials returned by a successful exchangeCode or refresh call

Store and protect these values in the application, since the SDK does not retain them or schedule refreshes. After a refresh, use the new credentials. The previous refresh token may no longer work

interface OAuthTokens
accessToken#

Bearer credential for delegated reads, not a bot token

string
expiresInSeconds#

Provider-reported lifetime in seconds, not an absolute expiry or a scheduled refresh

Fluxer names it expires_in

number
refreshToken#

Credential for an explicit refresh call, which may rotate it

string
scopes#

Granted scopes as returned by Fluxer

They can differ from requested scopes. Fluxer sends one space-separated scope string

readonly string[]
tokenType#

Authentication scheme for the access token

"Bearer"

Type Aliases#

OAuthScope#

One supported authorization scope from OAuthScopes

Returned token scopes remain strings because Fluxer can grant additional scopes unknown to this SDK

type OAuthScope = typeof OAuthScopes[keyof typeof OAuthScopes]type OAuthScope =
    typeof OAuthScopes[
      keyof typeof OAuthScopes
    ]