Skip to content

feat(backend): Add OAuth Application endpoints to Backend API client #5599

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 15, 2025
Merged
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
19 changes: 19 additions & 0 deletions .changeset/lucky-planets-drive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
'@clerk/backend': minor
---

Adds the ability to perform CRUD operations on OAuth Applications to the Backend API client.


```ts
import { createClerkClient } from '@clerk/backend';

const clerkClient = createClerkClient(...);

await clerkClient.oauthApplications.list({...});
await clerkClient.oauthApplications.get('templateId');
await clerkClient.oauthApplications.create({...});
await clerkClient.oauthApplications.update({...});
await clerkClient.oauthApplications.delete('templateId');
await clerkClient.oauthApplications.rotateSecret('templateId');
```
94 changes: 94 additions & 0 deletions packages/backend/src/api/endpoints/OAuthApplicationsApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import type { ClerkPaginationRequest } from '@clerk/types';

import { joinPaths } from '../../util/path';
import type { DeletedObject } from '../resources';
import type { PaginatedResourceResponse } from '../resources/Deserializer';
import type { OAuthApplication } from '../resources/OAuthApplication';
import { AbstractAPI } from './AbstractApi';

const basePath = '/oauth_applications';

type CreateOAuthApplicationParams = {
/**
* The name of the new OAuth application.
*
* @remarks Max length: 256
*/
name: string;
/**
* An array of redirect URIs of the new OAuth application
*/
redirectUris?: Array<string> | null | undefined;
/**
* Define the allowed scopes for the new OAuth applications that dictate the user payload of the OAuth user info endpoint. Available scopes are `profile`, `email`, `public_metadata`, `private_metadata`. Provide the requested scopes as a string, separated by spaces.
*/
scopes?: string | null | undefined;
/**
* If true, this client is public and you can use the Proof Key of Code Exchange (PKCE) flow.
*/
public?: boolean | null | undefined;
};

type UpdateOAuthApplicationParams = CreateOAuthApplicationParams & {
/**
* The ID of the OAuth application to update
*/
oauthApplicationId: string;
};

export class OAuthApplicationsApi extends AbstractAPI {
public async list(params: ClerkPaginationRequest = {}) {
return this.request<PaginatedResourceResponse<OAuthApplication[]>>({
method: 'GET',
path: basePath,
queryParams: params,
});
}

public async get(oauthApplicationId: string) {
this.requireId(oauthApplicationId);

return this.request<OAuthApplication>({
method: 'GET',
path: joinPaths(basePath, oauthApplicationId),
});
}

public async create(params: CreateOAuthApplicationParams) {
return this.request<OAuthApplication>({
method: 'POST',
path: basePath,
bodyParams: params,
});
}

public async update(params: UpdateOAuthApplicationParams) {
const { oauthApplicationId, ...bodyParams } = params;

this.requireId(oauthApplicationId);

return this.request<OAuthApplication>({
method: 'PATCH',
path: joinPaths(basePath, oauthApplicationId),
bodyParams,
});
}

public async delete(oauthApplicationId: string) {
this.requireId(oauthApplicationId);

return this.request<DeletedObject>({
method: 'DELETE',
path: joinPaths(basePath, oauthApplicationId),
});
}

public async rotateSecret(oauthApplicationId: string) {
this.requireId(oauthApplicationId);

return this.request<OAuthApplication>({
method: 'POST',
path: joinPaths(basePath, oauthApplicationId, 'rotate_secret'),
});
}
}
1 change: 1 addition & 0 deletions packages/backend/src/api/endpoints/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from './InvitationApi';
export * from './JwksApi';
export * from './JwtTemplatesApi';
export * from './OrganizationApi';
export * from './OAuthApplicationsApi';
export * from './PhoneNumberApi';
export * from './ProxyCheckApi';
export * from './RedirectUrlApi';
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/api/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
InvitationAPI,
JwksAPI,
JwtTemplatesApi,
OAuthApplicationsApi,
OrganizationAPI,
PhoneNumberAPI,
ProxyCheckAPI,
Expand Down Expand Up @@ -36,6 +37,7 @@ export function createBackendApiClient(options: CreateBackendApiOptions) {
invitations: new InvitationAPI(request),
jwks: new JwksAPI(request),
jwtTemplates: new JwtTemplatesApi(request),
oauthApplications: new OAuthApplicationsApi(request),
organizations: new OrganizationAPI(request),
phoneNumbers: new PhoneNumberAPI(request),
proxyChecks: new ProxyCheckAPI(request),
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/api/resources/Deserializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Invitation,
JwtTemplate,
OauthAccessToken,
OAuthApplication,
Organization,
OrganizationInvitation,
OrganizationMembership,
Expand Down Expand Up @@ -87,6 +88,8 @@ function jsonToObject(item: any): any {
return JwtTemplate.fromJSON(item);
case ObjectType.OauthAccessToken:
return OauthAccessToken.fromJSON(item);
case ObjectType.OAuthApplication:
return OAuthApplication.fromJSON(item);
case ObjectType.Organization:
return Organization.fromJSON(item);
case ObjectType.OrganizationInvitation:
Expand Down
20 changes: 20 additions & 0 deletions packages/backend/src/api/resources/JSON.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const ObjectType = {
Invitation: 'invitation',
JwtTemplate: 'jwt_template',
OauthAccessToken: 'oauth_access_token',
OAuthApplication: 'oauth_application',
Organization: 'organization',
OrganizationDomain: 'organization_domain',
OrganizationInvitation: 'organization_invitation',
Expand Down Expand Up @@ -194,6 +195,25 @@ export interface OauthAccessTokenJSON {
token_secret?: string;
}

export interface OAuthApplicationJSON extends ClerkResourceJSON {
object: typeof ObjectType.OAuthApplication;
id: string;
instance_id: string;
name: string;
client_id: string;
public: boolean;
scopes: string;
redirect_uris: Array<string>;
authorize_url: string;
token_fetch_url: string;
user_info_url: string;
discovery_url: string;
token_introspection_url: string;
created_at: number;
updated_at: number;
client_secret?: string;
}

export interface OrganizationJSON extends ClerkResourceJSON {
object: typeof ObjectType.Organization;
name: string;
Expand Down
41 changes: 41 additions & 0 deletions packages/backend/src/api/resources/OAuthApplication.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { OAuthApplicationJSON } from './JSON';

export class OAuthApplication {
constructor(
readonly id: string,
readonly instanceId: string,
readonly name: string,
readonly clientId: string,
readonly isPublic: boolean, // NOTE: `public` is reserved
readonly scopes: string,
readonly redirectUris: Array<string>,
readonly authorizeUrl: string,
readonly tokenFetchUrl: string,
readonly userInfoUrl: string,
readonly discoveryUrl: string,
readonly tokenIntrospectionUrl: string,
readonly createdAt: number,
readonly updatedAt: number,
readonly clientSecret?: string,
) {}

static fromJSON(data: OAuthApplicationJSON) {
return new OAuthApplication(
data.id,
data.instance_id,
data.name,
data.client_id,
data.public,
data.scopes,
data.redirect_uris,
data.authorize_url,
data.token_fetch_url,
data.user_info_url,
data.discovery_url,
data.token_introspection_url,
data.created_at,
data.updated_at,
data.client_secret,
);
}
}
1 change: 1 addition & 0 deletions packages/backend/src/api/resources/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export * from './Invitation';
export * from './JSON';
export * from './JwtTemplate';
export * from './OauthAccessToken';
export * from './OAuthApplication';
export * from './Organization';
export * from './OrganizationInvitation';
export * from './OrganizationMembership';
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export type {
InvitationJSON,
JwtTemplateJSON,
OauthAccessTokenJSON,
OAuthApplicationJSON,
OrganizationJSON,
OrganizationDomainJSON,
OrganizationDomainVerificationJSON,
Expand Down Expand Up @@ -102,6 +103,7 @@ export type {
Invitation,
JwtTemplate,
OauthAccessToken,
OAuthApplication,
Organization,
OrganizationDomain,
OrganizationDomainVerification,
Expand Down