{"record":{"id":"3926a2dc907b6637","repo":"AlistGo/alist","slug":"oidc-malformed-jwt-expected-3-parts-got-d","errorCode":null,"errorMessage":"oidc: malformed jwt, expected 3 parts got %d","messagePattern":"oidc: malformed jwt, expected 3 parts got (.+?)","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"server/handles/ssologin.go","lineNumber":178,"sourceCode":"\t\tSsoID:      userID,\n\t}\n\tif err = db.CreateUser(user); err != nil {\n\t\tif strings.HasPrefix(err.Error(), \"UNIQUE constraint failed\") && strings.HasSuffix(err.Error(), \"username\") {\n\t\t\tuser.Username = user.Username + \"_\" + userID\n\t\t\tif err = db.CreateUser(user); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t} else {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn user, nil\n}\n\nfunc parseJWT(p string) ([]byte, error) {\n\tparts := strings.Split(p, \".\")\n\tif len(parts) < 2 {\n\t\treturn nil, fmt.Errorf(\"oidc: malformed jwt, expected 3 parts got %d\", len(parts))\n\t}\n\tpayload, err := base64.RawURLEncoding.DecodeString(parts[1])\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"oidc: malformed jwt payload: %v\", err)\n\t}\n\treturn payload, nil\n}\n\nfunc OIDCLoginCallback(c *gin.Context) {\n\tuseCompatibility := setting.GetBool(conf.SSOCompatibilityMode)\n\tmethod := c.Query(\"method\")\n\tif useCompatibility {\n\t\tmethod = path.Base(c.Request.URL.Path)\n\t}\n\tclientId := setting.GetStr(conf.SSOClientId)\n\tendpoint := setting.GetStr(conf.SSOEndpointName)\n\tprovider, err := oidc.NewProvider(c, endpoint)\n\tif err != nil {","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/server/handles/ssologin.go#L160-L196","documentation":"Returned by parseJWT (server/handles/ssologin.go:178) during OIDC callback handling when the id_token has fewer than 2 dot-separated segments — i.e. it is not a JWT at all. Note the guard checks len(parts) < 2 while the message says 'expected 3 parts', so a token with zero or one dot triggers it; a normal JWT (header.payload.signature) passes.","triggerScenarios":"OIDC provider returns an opaque access token instead of a JWT id_token; the token string got truncated (e.g. by URL parsing on '#' or whitespace); SSO compatibility mode misreads the callback path so the wrong value is parsed as the token.","commonSituations":"Provider configuration requesting scope/response_type that omits id_token; proxies stripping query fragments; identity providers that return errors in the token field during outages.","solutions":["Verify the OIDC provider actually issues JWT id_tokens for your client","Inspect the raw token string at the callback for truncation or embedded whitespace","Log the segment count and the provider response to confirm which of the above applies","Check conf.SSOCompatibilityMode if the method is derived from the URL path"],"exampleFix":"// before\nidToken := resp.AccessToken // opaque token, no dots\n// after\nidToken := resp.IDToken // proper JWT: header.payload.signature","handlingStrategy":"validation","validationCode":"func looksLikeJWT(s string) bool { return strings.Count(s, \".\") >= 2 && len(s) > 0 }","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"malformed jwt\") { reFetchTokenAndRetry() }","preventionTips":["Pass resp.IDToken, not AccessToken, to JWT parsing","Verify providers issue JWT id_tokens before relying on claims parsing","Check tokens for truncation when crossing proxies"],"tags":["oidc","sso","jwt","auth"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}