Skip to content

Support usage of public clients when doing DCF auth flow - #101

Open
soulchainer wants to merge 3 commits into
Its-donkey:testfrom
soulchainer:feature/add-auth-client-type-logic
Open

Support usage of public clients when doing DCF auth flow#101
soulchainer wants to merge 3 commits into
Its-donkey:testfrom
soulchainer:feature/add-auth-client-type-logic

Conversation

@soulchainer

@soulchainer soulchainer commented Sep 1, 2026

Copy link
Copy Markdown

Description

Support usage of public clients when doing "Device Code Flow" (DCF) auth flow.

Current implementation doesn't allow refreshing tokens coming from a DCF with a public client. Those clients doesn't have a ClientSecret, neither need one (check official documentation here and here too).

Related Issues

Fixes #100

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Refactoring (no functional changes)
  • Test improvements

Changes Made

  • Add an AuthClientType and constants to differentiate the two types of OAuth clients allowed: confidential and public.
  • Add ClientType field in AuthConfig to determinate the type of client in use.
  • Update RefreshToken and PollDeviceToken to allow to refresh an access token received when finishing a DCF for authentication using a public client.
  • Change and add new tests to cover these changes.
  • Update documentation and CHANGELOG to reflect these changes.

Changelog

  • I have updated CHANGELOG.md with my changes

API Changes (if applicable)

Endpoint: GET/POST/etc helix/...

New Structs:

// Include any new or modified structs

Testing

  • Unit tests added/updated
  • Manual testing performed
  • All existing tests pass. Note: there is a flaky test that doesn't pass in my local environment... but it neither passed in local before these changes. It's flaky because it depends of one action to happen in some time and such actiones gets delayed like 1 sec in my env. I have a «fix» for this in another branch, but I guess this really is not «fixed» because, I guess, it passes in CI. In any case, I think such time should be increased, because it will continue «failing» with such constrain.

Test commands run:

go test ./...
go build ./...

Documentation

  • Documentation updated in docs/ folder
  • Code comments added for exported functions
  • README updated (if needed)

Checklist

  • My code follows the project's style guidelines
  • I have run go fmt on my code
  • I have run go vet and addressed any issues
  • My changes generate no new warnings
  • I have added tests that prove my fix/feature works
  • [x ] New and existing tests pass locally. Note: there is one flaky test that doesn't pass in my local environment... but it neither passes in local before these changes. It's flaky because it depends of one action to happen in some time and such actiones gets delayed like 1 sec in my env. I have a «fix» for this in another branch, but I guess this really is not «fixed» because, I guess, it passes in CI. In any case, I think such time should be increased, because it will continue «failing» with such constrain. So, if required, I can open a PR for that, but I think it's not already «fixed» because it passes in other environments (and increasing the timeout by 2 secs, also in mine).
  • I have updated documentation as needed

Screenshots (if applicable)

Additional Notes

This change is needed because doesn't having this is basically leaving out a whole kind of apps. Several desktop apps, cli apps, any device app which need to rely on the device code flow can't really depend on kappopher how it's right now, because refreshing a token it's not possible without this being fixed. So you would need to make the whole device code flow every single time you open your app or, depending of the kind of app and user, even before than that (access tokens for device code flow expire in 4 hours).

Please, be easy on me: I'm still learning Go and this is my first contribution to a Go project.

Note: I know the fix works, I'm already using it, but about the tests I «wrote»... There was a looooooot of code there, and basically mimicked related tests and changed them a bit, this is also my first approach to tests in go, and it's kinda... different. I really didn't know what to «test» exactly, more with tests that, in the original source, rely even directly in the Twitch official API endpoint (instead of mocks), due to the nature of such specific tests. Most (not all) of the tests I wrote here really felt redundant/not necessary.

github-actions Bot and others added 3 commits June 13, 2026 10:06
Release v1.4.0: Promote test to main
- Add an `AuthClientType` and constants to differentiate the two types
  of OAuth clients allowed: confidential and public.
- Add `ClientType` field in `AuthConfig` to determinate the type
  of client in use.
- Update `RefreshToken` and `PollDeviceToken` to allow to refresh
  an access token received when finishing a "Device Code Flow" (DCF)
  for authentication using a public client.
- Change and add new tests to cover these changes.
- Update `auth.md` documentation to cover these changes.
- Update CHANGELOG to reflect these changes.
Comment thread helix/auth.go
type AuthConfig struct {
ClientID string
ClientSecret string
ClientType AuthClientType

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

As ClientType defaults to ConfidentialClient, with falsy value 0, this means this change really doesn't really affect any existing code using kappopher, so people using it with confidential clients (looks like everyone till now) won't need to change it's code at all.

Comment thread helix/auth_test.go
config := AuthConfig{
ClientID: "test-client-id",
ClientSecret: "test-secret",
ClientType: ConfidentialClient,

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Conceptually this line is non necessary (because ConfidentialClient is the falsy/default value, so it would be set anyways to its correct value), I only added it to effectively check that nothing «breaks» because of it (even when, obviosly, shouldn't happen at all).

Comment thread helix/auth_test.go
}
}

func TestAuthClient_PollDeviceToken_PublicClient(t *testing.T) {

@soulchainer soulchainer Sep 2, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

With tests I was really a bit lost, about what to test and what not.
TestAuthClient_PollDeviceToken already test most of it and, conceptually, the client it's almost the same. So here I only tested the part that should differ, which is where it tests something related with the client secret. I could have dupped all the other code of TestAuthClient_PollDeviceToken here, but I considered it would be just that: duplicity, not needed.

The only thing I could think of test here is that the public client in fact doesn't have a client secret set and that polling the device token doesn't break.

@soulchainer soulchainer left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Some comments on code.

Comment thread helix/auth.go
}

if c.config.ClientSecret != "" {
if c.config.ClientType == ConfidentialClient && c.config.ClientSecret != "" {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

We only need to set the ClientSecret if we are using a confidential client, it doesn't makes sense to take this value into account if we're using a public client, as public clients doesn't have a client secret.

Comment thread helix/auth.go
"refresh_token": {refreshToken},
}

if c.config.ClientType == PublicClient {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Here I could have instead edited the url.Values, removing directly "client_secret" in line 417 and then only adding it if c.config.ClientType == ConfidentialClient, but, considering that probably (it seems so, at least, due to the full implementation of this) confidential clients are a bit more common, I thought the best would be to treat that as the default case and simply remove the value later if we're in fact dealing with a PublicClient.

Comment thread helix/auth_test.go
}
}

func TestAuthClient_RefreshToken_PublicClient(t *testing.T) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Same than the previous comment for this one.
So, given the way TestAuthClient_RefreshToken is using public official Twitch API endpoints directly, the only thing I thought I could check here is that, in fact, this code won't error saying the client secret is missing (because it shouldn't be taken into account for a public client). The way TestAuthClient_RefreshToken deals with this, for a public client this would error with a invalid clienterror (due to refreshToken and clientID being just fake strings, non valid real values, so the API would reject it, but I can't handle such error if it doesn't comes directly from this library or another one exporting a error value that can be checked/compared. So, instead of importing some error from Twitch itself (not possible), just checking this doesn't error with the error we don't expect it to error.

@soulchainer soulchainer left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant