-
Couldn't load subscription status.
- Fork 22
feat: support custom OIDC token endpoint paths for Zitadel, Entra ID, and other providers #271
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
base: main
Are you sure you want to change the base?
Changes from all commits
0077d28
02c8184
c069682
bd647cc
4004b97
d735172
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,11 @@ | ||
| # Changelog | ||
|
|
||
|
|
||
| ## [Unreleased](https://github.com/openfga/js-sdk/compare/v0.9.0...HEAD) | ||
| ## [Unreleased](https://github.com/openfga/sdk-generator#238) | ||
|
|
||
| ### Added | ||
| - Support for custom OIDC token endpoint paths (#141, #238) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you meant to reference openfga/sdk-generator#238? |
||
| - Compatibility with Zitadel, Entra ID, and other OIDC providers using non-standard token paths | ||
|
|
||
| ## v0.9.0 | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -144,6 +144,202 @@ const fgaClient = new OpenFgaClient({ | |
| }); | ||
| ``` | ||
|
|
||
| #### Default Behavior | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The information here is very specific to the token endpoint handling for the client credentials grant. Having a separate section would be a bit confusing it should be info within the client credentials section. |
||
| - When `apiTokenIssuer` is just a domain (e.g., `"auth.example.com"`), the SDK appends `/oauth/token` | ||
| - Example: `auth.example.com` → `https://auth.example.com/oauth/token` | ||
|
|
||
| #### Custom Token Paths | ||
| - When `apiTokenIssuer` includes a path, that path is used as-is | ||
| - Examples: | ||
| - `auth.example.com/oauth/v2` → `https://auth.example.com/oauth/v2` | ||
| - `https://auth.example.com/oauth/v2/token` → `https://auth.example.com/oauth/v2/token` | ||
|
|
||
| #### Provider Examples | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need to have specific vendor examples, general behavior documentation will be good. Also a full test file is not necessary just the token domain/path handling will suffice. |
||
|
|
||
| **Zitadel:** | ||
| ```javascript | ||
| apiTokenIssuer: 'https://auth.zitadel.example/oauth/v2/token' | ||
| ``` | ||
|
|
||
| Entra ID (Azure AD): | ||
| ``` | ||
| apiTokenIssuer: 'https://login.microsoftonline.com/tenant-id/oauth2/v2.0/token' | ||
| ``` | ||
|
|
||
| Auth0: | ||
|
|
||
| ``` | ||
| apiTokenIssuer: 'https://your-domain.auth0.com/oauth/token' | ||
| ``` | ||
| Create `tests/credentials-oidc.test.ts`: | ||
|
|
||
| ```typescript | ||
| import nock from 'nock'; | ||
| import { OpenFgaClient, UserClientConfigurationParams } from '../src'; | ||
| import { CredentialsMethod } from '../src/credentials'; | ||
| import { baseConfig } from './helpers/default-config'; | ||
|
|
||
| describe('OIDC Token Path Handling', () => { | ||
| beforeEach(() => { | ||
| nock.disableNetConnect(); | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| nock.cleanAll(); | ||
| nock.enableNetConnect(); | ||
| }); | ||
|
|
||
| const testOidcConfig = (apiTokenIssuer: string, expectedTokenUrl: string) => { | ||
| const config: UserClientConfigurationParams = { | ||
| ...baseConfig, | ||
| credentials: { | ||
| method: CredentialsMethod.ClientCredentials, | ||
| config: { | ||
| clientId: 'test-client', | ||
| clientSecret: 'test-secret', | ||
| apiTokenIssuer, | ||
| apiAudience: 'https://api.fga.example' | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| // Mock the token endpoint | ||
| const tokenScope = nock(expectedTokenUrl.split('/').slice(0, 3).join('/')) | ||
| .post(expectedTokenUrl.split('/').slice(3).join('/')) | ||
| .reply(200, { | ||
| access_token: 'test-token', | ||
| expires_in: 300 | ||
| }); | ||
|
|
||
| // Mock the FGA API call | ||
| const apiScope = nock('https://api.fga.example') | ||
| .post(`/stores/${baseConfig.storeId}/check`) | ||
| .reply(200, { allowed: true }); | ||
|
|
||
| return { config, tokenScope, apiScope }; | ||
| }; | ||
|
|
||
| it('should append /oauth/token when no path is provided', async () => { | ||
| const { config, tokenScope, apiScope } = testOidcConfig( | ||
| 'auth.example.com', | ||
| 'https://auth.example.com/oauth/token' | ||
| ); | ||
|
|
||
| const client = new OpenFgaClient(config); | ||
| await client.check({ | ||
| user: 'user:test', | ||
| relation: 'reader', | ||
| object: 'document:test' | ||
| }); | ||
|
|
||
| expect(tokenScope.isDone()).toBe(true); | ||
| expect(apiScope.isDone()).toBe(true); | ||
| }); | ||
|
|
||
| it('should respect custom token paths', async () => { | ||
| const { config, tokenScope, apiScope } = testOidcConfig( | ||
| 'auth.example.com/oauth/v2/token', | ||
| 'https://auth.example.com/oauth/v2/token' | ||
| ); | ||
|
|
||
| const client = new OpenFgaClient(config); | ||
| await client.check({ | ||
| user: 'user:test', | ||
| relation: 'reader', | ||
| object: 'document:test' | ||
| }); | ||
|
|
||
| expect(tokenScope.isDone()).toBe(true); | ||
| expect(apiScope.isDone()).toBe(true); | ||
| }); | ||
|
|
||
| it('should handle full URLs with custom paths', async () => { | ||
| const { config, tokenScope, apiScope } = testOidcConfig( | ||
| 'https://auth.example.com/oauth/v2/token', | ||
| 'https://auth.example.com/oauth/v2/token' | ||
| ); | ||
|
|
||
| const client = new OpenFgaClient(config); | ||
| await client.check({ | ||
| user: 'user:test', | ||
| relation: 'reader', | ||
| object: 'document:test' | ||
| }); | ||
|
|
||
| expect(tokenScope.isDone()).toBe(true); | ||
| expect(apiScope.isDone()).toBe(true); | ||
| }); | ||
|
|
||
| it('should handle paths with trailing slashes', async () => { | ||
| const { config, tokenScope, apiScope } = testOidcConfig( | ||
| 'auth.example.com/oauth/v2/', | ||
| 'https://auth.example.com/oauth/v2' | ||
| ); | ||
|
|
||
| const client = new OpenFgaClient(config); | ||
| await client.check({ | ||
| user: 'user:test', | ||
| relation: 'reader', | ||
| object: 'document:test' | ||
| }); | ||
|
|
||
| expect(tokenScope.isDone()).toBe(true); | ||
| expect(apiScope.isDone()).toBe(true); | ||
| }); | ||
|
|
||
| it('should handle root path correctly', async () => { | ||
| const { config, tokenScope, apiScope } = testOidcConfig( | ||
| 'auth.example.com/', | ||
| 'https://auth.example.com/oauth/token' | ||
| ); | ||
|
|
||
| const client = new OpenFgaClient(config); | ||
| await client.check({ | ||
| user: 'user:test', | ||
| relation: 'reader', | ||
| object: 'document:test' | ||
| }); | ||
|
|
||
| expect(tokenScope.isDone()).toBe(true); | ||
| expect(apiScope.isDone()).toBe(true); | ||
| }); | ||
|
|
||
| it('should work with Zitadel-style paths (/oauth/v2/token)', async () => { | ||
| const { config, tokenScope, apiScope } = testOidcConfig( | ||
| 'https://auth.zitadel.example/oauth/v2/token', | ||
| 'https://auth.zitadel.example/oauth/v2/token' | ||
| ); | ||
|
|
||
| const client = new OpenFgaClient(config); | ||
| await client.check({ | ||
| user: 'user:test', | ||
| relation: 'reader', | ||
| object: 'document:test' | ||
| }); | ||
|
|
||
| expect(tokenScope.isDone()).toBe(true); | ||
| expect(apiScope.isDone()).toBe(true); | ||
| }); | ||
|
|
||
| it('should work with Entra ID/Azure AD style paths', async () => { | ||
| const { config, tokenScope, apiScope } = testOidcConfig( | ||
| 'https://login.microsoftonline.com/tenant-id/oauth2/v2.0/token', | ||
| 'https://login.microsoftonline.com/tenant-id/oauth2/v2.0/token' | ||
| ); | ||
|
|
||
| const client = new OpenFgaClient(config); | ||
| await client.check({ | ||
| user: 'user:test', | ||
| relation: 'reader', | ||
| object: 'document:test' | ||
| }); | ||
|
|
||
| expect(tokenScope.isDone()).toBe(true); | ||
| expect(apiScope.isDone()).toBe(true); | ||
| }); | ||
| }); | ||
| ``` | ||
|
|
||
| ### Custom Headers | ||
|
|
||
| #### Default Headers | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This shouldn't be changed, it would now link out to an issue in the generator repo. My comment below about referencing
#238from the generator repo was intended for that line, not here.