-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Idp logto #5146
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?
Idp logto #5146
Conversation
- Introduced NETBIRD_MGMT_EXTRA_AUTH_AUDIENCE in base.setup.env for additional audience configuration. - Updated management.json.tmpl to include ExtraAuthAudience for LogTo integration. - Enhanced setup.env.exampale with LogTo-specific OIDC configuration and redirect URI instructions. - Modified idp.go to handle LogTo client credentials and resource URL construction for management API.
Merged 26 commits from main including: - Local user password change (embedded IdP) (netbirdio#5132) - [Management/Client] Trigger debug bundle runs from API/Dashboard - [client] Add support to wildcard custom records - Feature/embedded STUN - [management] Add custom dns zones - Various bug fixes and improvements Resolved conflict in idp/dex/provider.go by: - Keeping UpdateUserPassword method from main - Preserving connector management code from idp-logto (in connector.go)
|
jpmchia@terra-net.uk seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthroughThe pull request introduces Logto as a new identity provider for the system. Changes include configuration variables, template updates, documentation enhancements, and a complete Logto IdP manager implementation with user management operations and JWT token authentication. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Management Server
participant TokenMgr as LogtoCredentials
participant HTTP as HTTP Client
participant LogtoAPI as Logto API
Client->>TokenMgr: Authenticate()
TokenMgr->>TokenMgr: Check if JWT still valid
alt JWT expired or missing
TokenMgr->>HTTP: POST /oidc/token
HTTP->>LogtoAPI: Request JWT Token
LogtoAPI-->>HTTP: Return access_token, expires_in
HTTP-->>TokenMgr: Parse response
TokenMgr->>TokenMgr: Store token & expiry time
else JWT valid
TokenMgr-->>TokenMgr: Reuse cached token
end
TokenMgr-->>Client: Return JWTToken
Client->>HTTP: GET /api/users (with JWT header)
HTTP->>LogtoAPI: Fetch user data
LogtoAPI-->>HTTP: Return logtoProfile[]
HTTP->>Client: Map to UserData[]
sequenceDiagram
participant App as Management Server
participant Manager as LogtoManager
participant Creds as LogtoCredentials
participant LogtoAPI as Logto API
App->>Manager: CreateUser(email, name, accountID, invitedByEmail)
Manager->>Creds: Authenticate()
Creds->>LogtoAPI: Request JWT Token
LogtoAPI-->>Creds: Return access_token
Manager->>LogtoAPI: POST /api/users with JWT
LogtoAPI-->>Manager: Return created user profile
Manager->>Manager: Map logtoProfile to UserData
Manager-->>App: Return UserData
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ❌ 3❌ Failed checks (2 warnings, 1 inconclusive)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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.
Pull request overview
This pull request adds support for Logto as an identity provider (IDP) option in the NetBird management server. Logto is an open-source authentication and identity solution that can be used in both cloud and self-hosted (OSS) configurations. The implementation follows the existing patterns used by other IDP integrations like Keycloak and Zitadel.
Changes:
- Added complete Logto IDP integration with authentication, user management, and profile retrieval capabilities
- Included comprehensive test coverage for core authentication and JWT token management functionality
- Updated configuration templates and examples with Logto-specific documentation and setup instructions
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| management/server/idp/logto.go | Core implementation of Logto IDP manager with authentication, user CRUD operations, and profile management |
| management/server/idp/logto_test.go | Test suite covering manager initialization, JWT token handling, authentication flows, and profile data mapping |
| management/server/idp/idp.go | Integration of Logto into the IDP factory with configuration handling and resource URL construction |
| infrastructure_files/setup.env.example | Documentation and examples for Logto-specific configuration including OIDC endpoints, API resources, and redirect URIs |
| infrastructure_files/management.json.tmpl | Template updates to support Logto credentials and ExtraAuthAudience configuration |
| infrastructure_files/base.setup.env | Environment variable definitions for ExtraAuthAudience support |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| creds := LogtoCredentials{ | ||
| clientConfig: config, | ||
| helper: testCase.helper, | ||
| } |
Copilot
AI
Jan 21, 2026
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.
Inconsistent indentation. This closing brace is indented with tabs instead of being aligned with the opening of the struct definition above. It should be properly aligned at the same level as the creds := LogtoCredentials{ line.
| } | |
| } |
| return nil, err | ||
| } | ||
|
|
||
| var pending bool = true |
Copilot
AI
Jan 21, 2026
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.
Redundant variable declaration. The variable pending is explicitly declared as var pending bool = true. In Go, this can be simplified to pending := true which is more idiomatic and concise.
| var pending bool = true | |
| pending := true |
| if resp.StatusCode != http.StatusOK { | ||
| if lc.appMetrics != nil { | ||
| lc.appMetrics.IDPMetrics().CountRequestStatusError() | ||
| } | ||
| return nil, fmt.Errorf("unable to get logto token, statusCode %d", resp.StatusCode) | ||
| } |
Copilot
AI
Jan 21, 2026
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.
Missing metrics tracking for status code errors in requestJWTToken. Unlike the Authenticate method and other request methods (get, post, DeleteUser), this function doesn't call lc.appMetrics.IDPMetrics().CountRequestStatusError() before returning an error when the status code is not 200. This creates inconsistency in metrics tracking.
| creds := LogtoCredentials{ | ||
| clientConfig: config, | ||
| jwtToken: testCase.inputToken, | ||
| } |
Copilot
AI
Jan 21, 2026
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.
Inconsistent indentation. This closing brace is indented with tabs instead of being aligned with the opening of the struct definition above. It should be properly aligned at the same level as the creds := LogtoCredentials{ line.
| } | |
| } |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
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.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@management/server/idp/idp.go`:
- Around line 212-240: The resource default logic in the "logto" case builds a
cloud URL regardless of deployment; change it so when
config.ExtraConfig["Resource"] is empty you derive the resource from the
management endpoint for self-hosted deployments (or require explicit Resource):
inspect config.ExtraConfig["ManagementEndpoint"] and if present use its origin
(scheme+host[:port]) plus the API path (e.g. "/api") as the resource, otherwise
fall back to the tenant-based cloud URL; update the block that constructs
logtoClientConfig (LogtoClientConfig.Resource and TenantID) and ensure
NewLogtoManager receives the corrected LogtoClientConfig so token validation
matches the management endpoint.
In `@management/server/idp/logto.go`:
- Around line 180-210: In parseRequestJWTResponse, guard against malformed JWTs
before indexing into the dot-split payload: after obtaining jwtToken.AccessToken
call strings.Split(jwtToken.AccessToken, ".") and verify the resulting slice has
at least 2 (preferably 3) parts; if not, return a descriptive error instead of
indexing [1]; keep existing base64.RawURLEncoding.DecodeString and Unmarshal
logic but only run them after this length check so JWTToken and IssuedAt
handling won’t panic on invalid tokens.
- Around line 138-177: In requestJWTToken, the http response body is leaked on
non-200 paths; after lc.httpClient.Do(req) succeeds and before returning an
error when resp.StatusCode != http.StatusOK, ensure you close (and ideally
drain) resp.Body (e.g., io.Copy(io.Discard, resp.Body) then resp.Body.Close())
so the HTTP transport connection can be reused; leave the body open only when
returning the successful resp to the caller.
| case "logto": | ||
| logtoClientConfig := config.LogtoClientCredentials | ||
| if config.ClientConfig != nil { | ||
| // Build resource URL from tenant ID or use provided resource | ||
| resource := config.ExtraConfig["Resource"] | ||
| if resource == "" { | ||
| tenantID := config.ExtraConfig["TenantID"] | ||
| if tenantID == "" { | ||
| tenantID = "default" // Default for OSS | ||
| } | ||
| resource = fmt.Sprintf("https://%s.logto.app/api", tenantID) | ||
| } | ||
|
|
||
| logtoClientConfig = &LogtoClientConfig{ | ||
| ClientID: config.ClientConfig.ClientID, | ||
| ClientSecret: config.ClientConfig.ClientSecret, | ||
| GrantType: config.ClientConfig.GrantType, | ||
| TokenEndpoint: config.ClientConfig.TokenEndpoint, | ||
| ManagementEndpoint: config.ExtraConfig["ManagementEndpoint"], | ||
| Resource: resource, | ||
| TenantID: config.ExtraConfig["TenantID"], | ||
| } | ||
| } | ||
|
|
||
| if logtoClientConfig == nil { | ||
| return nil, fmt.Errorf("logto IdP configuration is missing") | ||
| } | ||
|
|
||
| return NewLogtoManager(*logtoClientConfig, appMetrics) |
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.
🧩 Analysis chain
🌐 Web query:
Logto client credentials flow resource audience configuration
💡 Result:
Short answer — use the OAuth2 token endpoint and pass the API identifier in the resource parameter (and scope as needed). For organization tokens also send organization_id.
How to:
- POST to /oidc/token with Authorization: Basic base64(client_id:client_secret) and form body:
- grant_type=client_credentials
- resource=<your API identifier, e.g. https://api.example.com>
- scope=<optional org/resource scopes or .default>
- (for org-level tokens) organization_id=
- The returned access_token will be issued for the resource/audience you specified; ensure your API validates that aud/resource matches this value. [1][2]
Sources:
- Logto docs — Protect organization-level API resources / client credentials flow. [1]
- Logto docs — Machine-to-machine (client credentials) overview. [2]
🏁 Script executed:
# Search for Logto-related configuration, tests, or documentation in the codebase
fd -type f -name "*.go" | xargs grep -l -i "logto" | head -20Repository: netbirdio/netbird
Length of output: 231
🏁 Script executed:
# Check for any Logto configuration examples or tests
rg -i "logto.*resource|resource.*logto" -A 3 -B 3 --type goRepository: netbirdio/netbird
Length of output: 4241
🏁 Script executed:
# Look for any comments or documentation about Logto setup
rg -i "logto.*app/api|tenant.*logto" -A 2 -B 2 --type goRepository: netbirdio/netbird
Length of output: 1992
Fix Logto resource default for self-hosted deployments.
The code defaults resource to https://{tenant-id}.logto.app/api regardless of deployment type. However, tests show self-hosted configurations use local endpoints (https://localhost:3001/...), creating a mismatch: the resource URL will still target the cloud .logto.app domain while token and management endpoints point to localhost. For self-hosted Logto, the resource must match what the management API validates; use the management endpoint host instead of the hardcoded cloud format, or require explicit Resource configuration with validation.
🤖 Prompt for AI Agents
In `@management/server/idp/idp.go` around lines 212 - 240, The resource default
logic in the "logto" case builds a cloud URL regardless of deployment; change it
so when config.ExtraConfig["Resource"] is empty you derive the resource from the
management endpoint for self-hosted deployments (or require explicit Resource):
inspect config.ExtraConfig["ManagementEndpoint"] and if present use its origin
(scheme+host[:port]) plus the API path (e.g. "/api") as the resource, otherwise
fall back to the tenant-based cloud URL; update the block that constructs
logtoClientConfig (LogtoClientConfig.Resource and TenantID) and ensure
NewLogtoManager receives the corrected LogtoClientConfig so token validation
matches the management endpoint.
| // requestJWTToken performs request to get jwt token. | ||
| // LogTo uses Basic Authentication in header (not client_id/client_secret in body like Keycloak). | ||
| func (lc *LogtoCredentials) requestJWTToken(ctx context.Context) (*http.Response, error) { | ||
| // Basic auth: base64(appId:appSecret) | ||
| auth := base64.StdEncoding.EncodeToString( | ||
| []byte(fmt.Sprintf("%s:%s", lc.clientConfig.ClientID, lc.clientConfig.ClientSecret)), | ||
| ) | ||
|
|
||
| data := url.Values{} | ||
| data.Set("grant_type", lc.clientConfig.GrantType) | ||
| data.Set("resource", lc.clientConfig.Resource) // Required parameter for LogTo | ||
| data.Set("scope", "all") | ||
|
|
||
| payload := strings.NewReader(data.Encode()) | ||
| req, err := http.NewRequest(http.MethodPost, lc.clientConfig.TokenEndpoint, payload) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| req.Header.Add("Authorization", "Basic "+auth) // Basic auth in header | ||
| req.Header.Add("content-type", "application/x-www-form-urlencoded") | ||
|
|
||
| log.WithContext(ctx).Debug("requesting new jwt token for logto idp manager") | ||
|
|
||
| resp, err := lc.httpClient.Do(req) | ||
| if err != nil { | ||
| if lc.appMetrics != nil { | ||
| lc.appMetrics.IDPMetrics().CountRequestError() | ||
| } | ||
|
|
||
| return nil, err | ||
| } | ||
|
|
||
| if resp.StatusCode != http.StatusOK { | ||
| if lc.appMetrics != nil { | ||
| lc.appMetrics.IDPMetrics().CountRequestStatusError() | ||
| } | ||
| return nil, fmt.Errorf("unable to get logto token, statusCode %d", resp.StatusCode) | ||
| } | ||
|
|
||
| return resp, nil |
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.
Close the token response body on non‑200 paths.
Early returns on error status leak the response body, which can exhaust the HTTP transport pool over time.
🩹 Suggested fix
if resp.StatusCode != http.StatusOK {
if lc.appMetrics != nil {
lc.appMetrics.IDPMetrics().CountRequestStatusError()
}
+ _, _ = io.Copy(io.Discard, resp.Body)
+ _ = resp.Body.Close()
return nil, fmt.Errorf("unable to get logto token, statusCode %d", resp.StatusCode)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // requestJWTToken performs request to get jwt token. | |
| // LogTo uses Basic Authentication in header (not client_id/client_secret in body like Keycloak). | |
| func (lc *LogtoCredentials) requestJWTToken(ctx context.Context) (*http.Response, error) { | |
| // Basic auth: base64(appId:appSecret) | |
| auth := base64.StdEncoding.EncodeToString( | |
| []byte(fmt.Sprintf("%s:%s", lc.clientConfig.ClientID, lc.clientConfig.ClientSecret)), | |
| ) | |
| data := url.Values{} | |
| data.Set("grant_type", lc.clientConfig.GrantType) | |
| data.Set("resource", lc.clientConfig.Resource) // Required parameter for LogTo | |
| data.Set("scope", "all") | |
| payload := strings.NewReader(data.Encode()) | |
| req, err := http.NewRequest(http.MethodPost, lc.clientConfig.TokenEndpoint, payload) | |
| if err != nil { | |
| return nil, err | |
| } | |
| req.Header.Add("Authorization", "Basic "+auth) // Basic auth in header | |
| req.Header.Add("content-type", "application/x-www-form-urlencoded") | |
| log.WithContext(ctx).Debug("requesting new jwt token for logto idp manager") | |
| resp, err := lc.httpClient.Do(req) | |
| if err != nil { | |
| if lc.appMetrics != nil { | |
| lc.appMetrics.IDPMetrics().CountRequestError() | |
| } | |
| return nil, err | |
| } | |
| if resp.StatusCode != http.StatusOK { | |
| if lc.appMetrics != nil { | |
| lc.appMetrics.IDPMetrics().CountRequestStatusError() | |
| } | |
| return nil, fmt.Errorf("unable to get logto token, statusCode %d", resp.StatusCode) | |
| } | |
| return resp, nil | |
| // requestJWTToken performs request to get jwt token. | |
| // LogTo uses Basic Authentication in header (not client_id/client_secret in body like Keycloak). | |
| func (lc *LogtoCredentials) requestJWTToken(ctx context.Context) (*http.Response, error) { | |
| // Basic auth: base64(appId:appSecret) | |
| auth := base64.StdEncoding.EncodeToString( | |
| []byte(fmt.Sprintf("%s:%s", lc.clientConfig.ClientID, lc.clientConfig.ClientSecret)), | |
| ) | |
| data := url.Values{} | |
| data.Set("grant_type", lc.clientConfig.GrantType) | |
| data.Set("resource", lc.clientConfig.Resource) // Required parameter for LogTo | |
| data.Set("scope", "all") | |
| payload := strings.NewReader(data.Encode()) | |
| req, err := http.NewRequest(http.MethodPost, lc.clientConfig.TokenEndpoint, payload) | |
| if err != nil { | |
| return nil, err | |
| } | |
| req.Header.Add("Authorization", "Basic "+auth) // Basic auth in header | |
| req.Header.Add("content-type", "application/x-www-form-urlencoded") | |
| log.WithContext(ctx).Debug("requesting new jwt token for logto idp manager") | |
| resp, err := lc.httpClient.Do(req) | |
| if err != nil { | |
| if lc.appMetrics != nil { | |
| lc.appMetrics.IDPMetrics().CountRequestError() | |
| } | |
| return nil, err | |
| } | |
| if resp.StatusCode != http.StatusOK { | |
| if lc.appMetrics != nil { | |
| lc.appMetrics.IDPMetrics().CountRequestStatusError() | |
| } | |
| _, _ = io.Copy(io.Discard, resp.Body) | |
| _ = resp.Body.Close() | |
| return nil, fmt.Errorf("unable to get logto token, statusCode %d", resp.StatusCode) | |
| } | |
| return resp, nil |
🤖 Prompt for AI Agents
In `@management/server/idp/logto.go` around lines 138 - 177, In requestJWTToken,
the http response body is leaked on non-200 paths; after lc.httpClient.Do(req)
succeeds and before returning an error when resp.StatusCode != http.StatusOK,
ensure you close (and ideally drain) resp.Body (e.g., io.Copy(io.Discard,
resp.Body) then resp.Body.Close()) so the HTTP transport connection can be
reused; leave the body open only when returning the successful resp to the
caller.
| // parseRequestJWTResponse parses jwt raw response body and extracts token and expires in seconds. | ||
| func (lc *LogtoCredentials) parseRequestJWTResponse(rawBody io.ReadCloser) (JWTToken, error) { | ||
| jwtToken := JWTToken{} | ||
| body, err := io.ReadAll(rawBody) | ||
| if err != nil { | ||
| return jwtToken, err | ||
| } | ||
|
|
||
| err = lc.helper.Unmarshal(body, &jwtToken) | ||
| if err != nil { | ||
| return jwtToken, err | ||
| } | ||
|
|
||
| if jwtToken.ExpiresIn == 0 && jwtToken.AccessToken == "" { | ||
| return jwtToken, fmt.Errorf("error while reading response body, expires_in: %d and access_token: %s", jwtToken.ExpiresIn, jwtToken.AccessToken) | ||
| } | ||
|
|
||
| data, err := base64.RawURLEncoding.DecodeString(strings.Split(jwtToken.AccessToken, ".")[1]) | ||
| if err != nil { | ||
| return jwtToken, err | ||
| } | ||
|
|
||
| // Exp maps into exp from jwt token | ||
| var IssuedAt struct{ Exp int64 } | ||
| err = lc.helper.Unmarshal(data, &IssuedAt) | ||
| if err != nil { | ||
| return jwtToken, err | ||
| } | ||
| jwtToken.expiresInTime = time.Unix(IssuedAt.Exp, 0) | ||
|
|
||
| return jwtToken, nil |
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.
Guard against malformed JWTs to avoid panic.
strings.Split(...)[1] panics if the token doesn’t contain a payload segment. Add a length check before indexing.
🛡️ Suggested fix
- data, err := base64.RawURLEncoding.DecodeString(strings.Split(jwtToken.AccessToken, ".")[1])
+ parts := strings.Split(jwtToken.AccessToken, ".")
+ if len(parts) < 2 {
+ return jwtToken, fmt.Errorf("invalid access token format")
+ }
+ data, err := base64.RawURLEncoding.DecodeString(parts[1])📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // parseRequestJWTResponse parses jwt raw response body and extracts token and expires in seconds. | |
| func (lc *LogtoCredentials) parseRequestJWTResponse(rawBody io.ReadCloser) (JWTToken, error) { | |
| jwtToken := JWTToken{} | |
| body, err := io.ReadAll(rawBody) | |
| if err != nil { | |
| return jwtToken, err | |
| } | |
| err = lc.helper.Unmarshal(body, &jwtToken) | |
| if err != nil { | |
| return jwtToken, err | |
| } | |
| if jwtToken.ExpiresIn == 0 && jwtToken.AccessToken == "" { | |
| return jwtToken, fmt.Errorf("error while reading response body, expires_in: %d and access_token: %s", jwtToken.ExpiresIn, jwtToken.AccessToken) | |
| } | |
| data, err := base64.RawURLEncoding.DecodeString(strings.Split(jwtToken.AccessToken, ".")[1]) | |
| if err != nil { | |
| return jwtToken, err | |
| } | |
| // Exp maps into exp from jwt token | |
| var IssuedAt struct{ Exp int64 } | |
| err = lc.helper.Unmarshal(data, &IssuedAt) | |
| if err != nil { | |
| return jwtToken, err | |
| } | |
| jwtToken.expiresInTime = time.Unix(IssuedAt.Exp, 0) | |
| return jwtToken, nil | |
| // parseRequestJWTResponse parses jwt raw response body and extracts token and expires in seconds. | |
| func (lc *LogtoCredentials) parseRequestJWTResponse(rawBody io.ReadCloser) (JWTToken, error) { | |
| jwtToken := JWTToken{} | |
| body, err := io.ReadAll(rawBody) | |
| if err != nil { | |
| return jwtToken, err | |
| } | |
| err = lc.helper.Unmarshal(body, &jwtToken) | |
| if err != nil { | |
| return jwtToken, err | |
| } | |
| if jwtToken.ExpiresIn == 0 && jwtToken.AccessToken == "" { | |
| return jwtToken, fmt.Errorf("error while reading response body, expires_in: %d and access_token: %s", jwtToken.ExpiresIn, jwtToken.AccessToken) | |
| } | |
| parts := strings.Split(jwtToken.AccessToken, ".") | |
| if len(parts) < 2 { | |
| return jwtToken, fmt.Errorf("invalid access token format") | |
| } | |
| data, err := base64.RawURLEncoding.DecodeString(parts[1]) | |
| if err != nil { | |
| return jwtToken, err | |
| } | |
| // Exp maps into exp from jwt token | |
| var IssuedAt struct{ Exp int64 } | |
| err = lc.helper.Unmarshal(data, &IssuedAt) | |
| if err != nil { | |
| return jwtToken, err | |
| } | |
| jwtToken.expiresInTime = time.Unix(IssuedAt.Exp, 0) | |
| return jwtToken, nil |
🤖 Prompt for AI Agents
In `@management/server/idp/logto.go` around lines 180 - 210, In
parseRequestJWTResponse, guard against malformed JWTs before indexing into the
dot-split payload: after obtaining jwtToken.AccessToken call
strings.Split(jwtToken.AccessToken, ".") and verify the resulting slice has at
least 2 (preferably 3) parts; if not, return a descriptive error instead of
indexing [1]; keep existing base64.RawURLEncoding.DecodeString and Unmarshal
logic but only run them after this length check so JWTToken and IssuedAt
handling won’t panic on invalid tokens.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
|
@jpmchia have you evaluated logto with the new embedded IdP? You can use the generic OIDC flow: https://docs.netbird.io/selfhosted/identity-providers#adding-an-identity-provider There are a few advantages for that, because we don't need an IdP manager in this mode |




Describe your changes
Issue ticket number and link
Stack
Checklist
Documentation
Select exactly one:
Docs PR URL (required if "docs added" is checked)
Paste the PR link from https://github.com/netbirdio/docs here:
https://github.com/netbirdio/docs/pull/__
Summary by CodeRabbit
New Features
Chores
✏️ Tip: You can customize this high-level summary in your review settings.