Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: add client credentials OAuth2 applications for API access#18846

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
ThomasK33 wants to merge1 commit intothomask33/07-12-feat_oauth2_add_client_credentials_grant_type_and_user_ownership
base:thomask33/07-12-feat_oauth2_add_client_credentials_grant_type_and_user_ownership
Choose a base branch
Loading
fromthomask33/07-14-feat_oauth2_add_frontend_ui_for_client_credentials_applications

Conversation

@ThomasK33
Copy link
Member

Add OAuth2 Client Credentials Grant Type Support

This PR adds support for OAuth2 client credentials grant type, enabling server-to-server API access. Key changes include:

  • Added new fields to OAuth2ProviderApp model:created_at,grant_types, anduser_id
  • Addedcreated_at field to OAuth2ProviderAppSecret model
  • Added revocation endpoint to OAuth2AppEndpoints
  • Created new UI components for managing client credentials applications:
    • ClientCredentialsAppForm for creating/editing applications
    • ClientCredentialsAppRow for displaying applications in a table
  • Added new routes for client credentials applications:
    • /settings/oauth2-provider/new for creating new applications
    • /settings/oauth2-provider/:appId for managing existing applications
  • Redesigned OAuth2 applications settings page to separate owned applications from authorized applications
  • Enhanced secret creation dialog with code examples for API usage
  • Updated API documentation to reflect new fields and endpoints

This implementation provides a more secure alternative to long-lived API tokens by allowing users to create OAuth2 applications with client credentials grant type for server-to-server authentication.

@ThomasK33Graphite App
Copy link
MemberAuthor

ThomasK33 commentedJul 14, 2025
edited
Loading

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stackon Graphite.
Learn more

This stack of pull requests is managed byGraphite. Learn more aboutstacking.

@ThomasK33ThomasK33force-pushed thethomask33/07-12-feat_oauth2_add_client_credentials_grant_type_and_user_ownership branch fromc654948 toeac2681CompareJuly 14, 2025 16:22
@ThomasK33ThomasK33force-pushed thethomask33/07-14-feat_oauth2_add_frontend_ui_for_client_credentials_applications branch from8c29819 to168176bCompareJuly 14, 2025 16:22
@ThomasK33ThomasK33force-pushed thethomask33/07-12-feat_oauth2_add_client_credentials_grant_type_and_user_ownership branch fromeac2681 toac0d1f6CompareJuly 14, 2025 17:18
@ThomasK33ThomasK33force-pushed thethomask33/07-14-feat_oauth2_add_frontend_ui_for_client_credentials_applications branch 2 times, most recently from4fcf5b1 to65b1054CompareJuly 14, 2025 17:46
@ThomasK33ThomasK33 marked this pull request as ready for reviewJuly 14, 2025 18:09
@BrunoQuaresma
Copy link
Contributor

I'm doing the review, but I'm really missing a video demoing the feature, or at least screenshots.

ThomasK33 reacted with thumbs up emoji

@ThomasK33Graphite App
Copy link
MemberAuthor

I'm doing the review, but I'm really missing a video demoing the feature, or at least screenshots.

I’ll add some screenshots tomorrow

BrunoQuaresma reacted with thumbs up emoji

Copy link
Contributor

@BrunoQuaresmaBrunoQuaresma left a comment
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Thanks for working on the FE 🙏 👏

Since there are a good amount of changes, and I want to be sure I'm going to review them properly, and not overwhelming you, I think it would make sense to run a few review rounds.

For the first round, I pointed one blocker, and a few major improvements we can do. After we work on that, I will run another review and see if there is any minor left.

Sounds good?

};

getOAuth2ProviderApps=async(
filter?:TypesGen.OAuth2ProviderAppFilter,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I think we can simplify this function by just making the filter not optional and adding a default value as{} so we could use it directly in theURLSearchParams without having to do many ifs.

getOAuth2ProviderApps=async(filter:TypesGen.OAuth2ProviderAppFilter={},):Promise<TypesGen.OAuth2ProviderApp[]>=>{constparams=newURLSearchParams(filter);constresp=awaitthis.axios.get("/api/v2/oauth2-provider/apps",{ params},);returnresp.data;};

ThomasK33 reacted with thumbs up emoji
};

exportconstgetApps=(userId?:string)=>{
exportconstgetApps=(filter?:{user_id?:string;owner_id?:string})=>{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Another function we can improve.

exportconstgetApps=(filter:OAuth2ProviderAppFilter={})=>{return{queryKey:[...appsKey,filter],queryFn:()=>API.getOAuth2ProviderApps(filter),};};

ThomasK33 reacted with thumbs up emoji
readonlyicon:string;
readonlycreated_at:string;
readonlygrant_types:readonlystring[];
readonlyuser_id?:string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Is it possible to have anOAuth2ProviderApp without an user?

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

yes, that's possible

BrunoQuaresma reacted with thumbs up emoji
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

What is the use case?

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Different flows/grant types. Client credential flows have to be owned/tied to a user, while authorization code flow must not be tied to a user.

BrunoQuaresma reacted with thumbs up emoji
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Do you think would make sense to left a comment in this attribute, in the codersdk type? I think this is something I would easily forget in the future.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

My overall feeling is this component is doing way more than a form should do. I think we can improve by:

  • Remove props that are not going to change and use the value directly
  • Execute the mutation in the form, and only call theonSubmit oronSubmitSuccess if the parent needs that

By applying these, I think we can significantly reduce the complexity of this component. This may require a few changes, so probably will need a second review round.

ThomasK33 reacted with thumbs up emoji
event.preventDefault();
constformData=newFormData(event.targetasHTMLFormElement);

onSubmit({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I know using the WEB API is nice, but it has some gaps and to make the code consistent lets useFormik andYup for validation.

You can easily find examples on how to use both in the codebase.

ThomasK33 reacted with thumbs up emoji
const[showCodeExample,setShowCodeExample]=useState(false);

// Fetch users and find the owner by user_id
constusersQuery=useQuery({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

⚠️ Blocker

The BE supports filtering a user by username or email, if that is not available in the app, I think we can do.

  • Add the username or user email to the app response
  • Add an endpoint into the backend to fetch user data by ID

I don't think we should move forward with the current solution.

ThomasK33 reacted with thumbs up emoji
hideCancel
{fullNewSecret&&app&&(
<Dialog
css={{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

We should avoid adding a new dialog style only for this case. Did you take a look in the other dialogs? Wondering what you want to achieve here that is not achievable by using the existent dialogs.

ThomasK33 reacted with thumbs up emoji
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I can refactor that into a component if you'd like, but none of the other dialogs allow resizing since the max width is always hardcoded.

When opening the code example in the dialog, I want it to expand both horizontally and vertically so that the word wrap on the curl command in the code example doesn't apply, and users can see the command they are about to copy and paste without having first to copy it and then inspect it in another editor.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Ok, lets keep it as it is - in terms of design. I will give it a try and see what we can do to better support this use case in our design system.

OAuth2 client secret
</h3>
<div
css={{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

We are not using inline CSS styles anymore. Give a try toTailwindCSS class names. You can see many of them in the code base.

ThomasK33 reacted with thumbs up emoji
}
/>
<divcss={{marginTop:24}}>
<Link
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Looks like it should be a button instead of a link. Links should be used for navigation.

ThomasK33 reacted with thumbs up emoji
SSH Keys
</SidebarNavItem>
<SidebarNavItemhref="tokens"icon={KeyIcon}>
<SidebarNavItemhref="tokens"icon={KeyIcon}end={false}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Wondering why only these two have end set to false 🤔

ThomasK33 reacted with thumbs up emoji
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

None of the other items has nested menus. Only the tokens and OAuth2 app section can navigate to another route, thus making the nav item "unselected".

BrunoQuaresma reacted with eyes emoji
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I will give this a try and see how it behaves.

Valid:true,
})
iferr!=nil {
httpapi.InternalServerError(rw,err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Let's also add a log here; we probably don't want to leak the error details back in the HTTP response, so let's just say something generic; admins can refer to the logs for details.

ThomasK33 reacted with thumbs up emoji
Copy link
Contributor

@dannykoppingdannykoppingJul 16, 2025
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

You're still returning the error directly; we probably don't want to do that; ditto for the other cases.

@ThomasK33ThomasK33force-pushed thethomask33/07-14-feat_oauth2_add_frontend_ui_for_client_credentials_applications branch from65b1054 tof044533CompareJuly 15, 2025 17:27
@ThomasK33ThomasK33force-pushed thethomask33/07-12-feat_oauth2_add_client_credentials_grant_type_and_user_ownership branch fromac0d1f6 toef3c66eCompareJuly 15, 2025 17:27
@ThomasK33ThomasK33force-pushed thethomask33/07-14-feat_oauth2_add_frontend_ui_for_client_credentials_applications branch 2 times, most recently from5f71ebd toc84c4beCompareJuly 17, 2025 13:30
@ThomasK33
Copy link
MemberAuthor

ThomasK33 commentedJul 17, 2025
edited
Loading

@BrunoQuaresma, sorry it took me a while, but here's a video showing off the client_credentials UI, including the expanding dialog:

coder_client_credentials_ui.mp4

@ThomasK33ThomasK33force-pushed thethomask33/07-12-feat_oauth2_add_client_credentials_grant_type_and_user_ownership branch fromef3c66e to3f1495cCompareJuly 17, 2025 14:38
@ThomasK33ThomasK33force-pushed thethomask33/07-14-feat_oauth2_add_frontend_ui_for_client_credentials_applications branch fromc84c4be to40d7fd1CompareJuly 17, 2025 14:38
Copy link
Contributor

@BrunoQuaresmaBrunoQuaresma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Thanks for working in all my review comments. I think it is good to go 👍

PS: I think the UX is ok and there are a few improvements we can do. Ideally, we would have a design phase when adding or changing features.

@ThomasK33ThomasK33force-pushed thethomask33/07-14-feat_oauth2_add_frontend_ui_for_client_credentials_applications branch from40d7fd1 tocc78865CompareJuly 23, 2025 16:53
@ThomasK33ThomasK33 requested a review fromEmyrk as acode ownerJuly 23, 2025 16:53
@ThomasK33ThomasK33force-pushed thethomask33/07-12-feat_oauth2_add_client_credentials_grant_type_and_user_ownership branch from3f1495c to2fc275dCompareJuly 23, 2025 16:53
@github-actionsgithub-actionsbot added the staleThis issue is like stale bread. labelJul 31, 2025
@ThomasK33ThomasK33 assignedEmyrk and unassignedThomasK33Aug 12, 2025
@ThomasK33ThomasK33 removed the staleThis issue is like stale bread. labelAug 12, 2025
@ThomasK33ThomasK33 reopened thisAug 12, 2025
@ThomasK33ThomasK33force-pushed thethomask33/07-14-feat_oauth2_add_frontend_ui_for_client_credentials_applications branch fromcc78865 to4ce585eCompareAugust 12, 2025 16:33
@github-actionsgithub-actionsbot added the staleThis issue is like stale bread. labelAug 20, 2025
@EmyrkEmyrk reopened thisAug 24, 2025
@github-actionsgithub-actionsbot removed the staleThis issue is like stale bread. labelAug 25, 2025
@github-actionsgithub-actionsbot added the staleThis issue is like stale bread. labelSep 1, 2025
@ThomasK33ThomasK33 deleted the thomask33/07-14-feat_oauth2_add_frontend_ui_for_client_credentials_applications branchSeptember 15, 2025 07:52
@ThomasK33ThomasK33 restored the thomask33/07-14-feat_oauth2_add_frontend_ui_for_client_credentials_applications branchSeptember 15, 2025 07:53
@EmyrkEmyrk reopened thisOct 6, 2025
Makefile Outdated

test:
$(GIT_FLAGS) gotestsum --format standard-quiet$(GOTESTSUM_RETRY_FLAGS) --packages="./..." -- -v -short -count=1$(if$(RUN),-run$(RUN))
$(GIT_FLAGS) gotestsum --format standard-quiet$(GOTESTSUM_RETRY_FLAGS) --packages="$(if$(PACKAGE),$(PACKAGE),./...)" -- -v -short -count=1$(if$(RUN),-run$(RUN))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Why do we need this?

@ThomasK33ThomasK33force-pushed thethomask33/07-14-feat_oauth2_add_frontend_ui_for_client_credentials_applications branch from4ce585e tofd42ee1CompareOctober 10, 2025 14:42
@ThomasK33ThomasK33force-pushed thethomask33/07-12-feat_oauth2_add_client_credentials_grant_type_and_user_ownership branch 2 times, most recently from487fa65 toc5bbc6eCompareOctober 10, 2025 14:53
@ThomasK33ThomasK33force-pushed thethomask33/07-14-feat_oauth2_add_frontend_ui_for_client_credentials_applications branch fromfd42ee1 to65adff3CompareOctober 10, 2025 14:53
- Add ClientCredentialsAppForm and ClientCredentialsAppRow components- Update API schemas to include created_at, grant_types, and user_id fields- Add dedicated pages for creating and managing client credentials apps- Update sidebar navigation and routing for OAuth2 client credentials- Enhance OAuth2AppPageView with user ownership information displayChange-Id: I3271c7fb995d7225dd6cc830066fa2c8cb29720aSigned-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33ThomasK33force-pushed thethomask33/07-12-feat_oauth2_add_client_credentials_grant_type_and_user_ownership branch fromc5bbc6e toc659bbcCompareOctober 10, 2025 14:57
@ThomasK33ThomasK33force-pushed thethomask33/07-14-feat_oauth2_add_frontend_ui_for_client_credentials_applications branch from65adff3 to7d8adcaCompareOctober 10, 2025 14:57
Copy link
Contributor

@buenos-nachosbuenos-nachos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Did a quick scan since I figured that this PR has had a decent amount of review already. My main concern was around some of the conditional rendering logic

:"";

constresp=awaitthis.axios.get(`/api/v2/oauth2-provider/apps?${params}`);
constparams=newURLSearchParams(filterasRecord<string,string>);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Nit: while it shouldn't be an issue now, I'd prefer it if we avoid the type assertion, since this will silence any type mismatches in the future. How confident are we that we won't need to support non-strings values in the future?

I feel like the Go way of doing things makes sense here: copy the values from the filter into a new object that's passed directly to the constructor:

newURLSearchParams({user_id:filter.user_id??"",owner_id:filter.owner_id??""});

</Button>
</Stack>

<formclassName="mt-2.5"onSubmit={form.handleSubmit}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Nit:mt-2.5 can be swapped forpt-2.5, and should be less likely to cause layout side effects from margin collapse

<divclassName="font-medium mb-1">
About Client Credentials Applications
</div>
<divclassName="text-sm">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This long-form text should be ap tag

Comment on lines +46 to +49
if(!appId){
navigate("/settings/oauth2-provider");
returnnull;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Two big problems here:

  1. navigate is a side effect function, and shouldn't be called from inside a render like this.
  2. The conditional rendering breaksReact's rules of hooks. In practice, I don't see this code breaking, because the app ID shouldn't be able to change without a full page change, but breaking this rule in general can cause a React component to completely blow up and trigger our global error boundary. I don't feel comfortable with having code that the React team actively pushes you away from, because the justification here feels like "just trust me bro"

Two changes we can do:

  1. Add auseEffect call to handle the navigation
    useEffect(()=>{if(!appId){navigate("/settings/oauth2-provider");}},[navigate,appId]);
  2. Anywhere after all the hooks mount, do conditional rendering that instead returns out theNavigate component (which is basically a wrapper overuseNavigate+useEffect under the hood)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

React Router has a weird policy of not stabilizing their function references from their hooks, so if throwingnavigate in the dependency array causes too many renders (or worse, infinite renders), you can do this instead:

conststableNavigate=useEffectEvent(navigate);useEffect(()=>{if(!appId){stableNavigate("/settings/oauth2-provider");}},[stableNavigate,appId]);

Comment on lines +69 to +74
consthandleUpdateApp=async(data:{
name:string;
icon:string;
grant_types:TypesGen.OAuth2ProviderGrantType[];
redirect_uris:string[];
})=>{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This type definition is used in a few different places, and it's complicated enough that I feel like it'd be better to extract it into a custom type alias

constappToDelete=ownedApps.find((app)=>app.id===appIdToDelete);

consthandleManageOwnedApp=(app:TypesGen.OAuth2ProviderApp)=>{
navigate(`${app.id}`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Is there a reason for the template literal?

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@dannykoppingdannykoppingdannykopping approved these changes

@EmyrkEmyrkEmyrk left review comments

+2 more reviewers

@buenos-nachosbuenos-nachosbuenos-nachos left review comments

@BrunoQuaresmaBrunoQuaresmaBrunoQuaresma approved these changes

Reviewers whose approvals may not affect merge requirements

Assignees

@EmyrkEmyrk

Labels

staleThis issue is like stale bread.

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

6 participants

@ThomasK33@BrunoQuaresma@dannykopping@Emyrk@buenos-nachos

[8]ページ先頭

©2009-2025 Movatter.jp