Documentation
ReferenceJavaScript SDKNamespacesauth.oauth

auth.oauth

Your project as an OAuth 2.1 and OpenID Connect provider.

Two different things live under this namespace, and conflating them is the main source of confusion.

Consumer side — the applications your users authorized, which they can review and disconnect. One screen in your settings.

Provider side — your project acting as the issuer, so other applications can "sign in with you". A whole flow you host, walked through in OAuth Server — this page is the method reference for it.

Neither is auth.authorize(provider), which sends your user out to Google. That is the opposite direction: you as the consumer of someone else's identity.

grants.list

oauth.grants.list() → OAuthUserGrant[]

The applications the current user has authorized.

const { data: apps } = await nulvless.auth.oauth.grants.list();
client{ id, name?, logo_uri?, uri? }Required

Enough to render the app with its own name and logo rather than a bare id.

scopesstring[]Required

What this application was granted. Show them — "has access to your profile and email" is the whole point of the screen.

granted_atstringRequired

These live under oauth.grants, not directly on oauth.

grants.revoke

oauth.grants.revoke(clientId) → null

Disconnects one application. Its tokens stop working; the others are untouched.

await nulvless.auth.oauth.grants.revoke(clientId);
clientIdstringRequired

From client.id above.

discovery

discovery() → OAuthDiscovery

The OIDC discovery document — what any conforming client reads to configure itself.

const { data } = await nulvless.auth.oauth.discovery();

jwks

jwks() → OAuthJWKS

The public keys your tokens are verified against.

const { data: keys } = await nulvless.auth.oauth.jwks();

Both are published from the moment the OAuth server is enabled and stay in sync with your settings. There is nothing to regenerate when you change them, and nothing to host yourself.

authorize

authorize(params) → { url }

Begins the authorization code flow, with PKCE.

await nulvless.auth.oauth.authorize({
  client_id,
  redirect_uri: 'https://client.example.com/callback',
  code_challenge,
  code_challenge_method: 'S256',
  scope: 'openid profile',
  state,
});
client_idstringRequired
redirect_uristringRequired

Must be on the application's allowlist. Checked before anything is issued.

code_challengestringRequired

PKCE is not optional here.

code_challenge_method'S256'Required

The only accepted method. plain is refused.

response_type'code'Optional
scopestringOptional

Space-separated.

statestringOptional

Round-tripped back to your callback. Use it, and check it.

resourcestringOptional
noncestringOptional

The OIDC nonce, tying an ID token to this request. Unrelated to the sudo nonce used elsewhere in this SDK.

exchangeCodeForToken

exchangeCodeForToken(params) → OAuthTokenResponse

Trades the authorization code for tokens.

const { data } = await nulvless.auth.oauth.exchangeCodeForToken({
  code,
  code_verifier,
});
codestringRequired
code_verifierstringRequired

The PKCE counterpart of code_challenge.

redirect_uristringOptional
client_idstringOptional
client_secretstringOptional

Confidential clients only. Never from a browser — a public client has no secret precisely because it cannot keep one.

resourcestringOptional

refreshToken

refreshToken(params) → OAuthTokenResponse

const { data } = await nulvless.auth.oauth.refreshToken({ refresh_token });
refresh_tokenstringRequired
client_idstringOptional
client_secretstringOptional
Confidential clients only.

getAuthorization

getAuthorization(authorizationId) → OAuthAuthorization

Loads a pending authorization so you can render the consent screen — who is asking, and for what.

const { data: pending } = await nulvless.auth.oauth.getAuthorization(
  authorizationId,
);

consent(authorizationId, action) → OAuthConsentResult

Records the user's answer.

await nulvless.auth.oauth.consent(authorizationId, 'approve');
await nulvless.auth.oauth.consent(authorizationId, 'deny');
authorizationIdstringRequired
actionOAuthConsentActionRequired
approveIssue the code and continue the flow.
denyRefuse. The client is redirected back with an error.

The screen is entirely yours — your name, your styling, your copy. What the platform guarantees is that nothing is issued until consent says so, so there is no way to accidentally grant access by rendering the page.

userinfo

userinfo() → OAuthUserInfo

The OIDC UserInfo claims for the current access token. Called by the client application with the token it was issued, not by you.

const { data: claims } = await nulvless.auth.oauth.userinfo();

register

register(params) → OAuthApp

Dynamic client registration, per RFC 7591 — applications registering themselves rather than being created by you.

const { data: app } = await nulvless.auth.oauth.register(params);

Off unless you enable it. With it off, applications are created through auth.admin.oauthClients, which is the right default: dynamic registration lets anyone who can reach the endpoint create a client.

What a third party can ask for

An application never receives more than the user themselves holds. A token issued to a third party carries a subset of the user's own scopes — so granting an integration projects:write does nothing if that user could not write projects to begin with.

This is worth stating on your consent screen. "This app is requesting X" is misleading if the user cannot do X either.

Next

On this page