{"record":{"id":"eb71287f107cfa94","repo":"wavetermdev/waveterm","slug":"no-context-found-in-jwt-token","errorCode":null,"errorMessage":"no context found in jwt token","messagePattern":"no context found in jwt token","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/wshutil/wshrouter_controlimpl.go","lineNumber":294,"sourceCode":"\t\t}\n\t\t_, err := wshRpc.SendRpcRequest(wshrpc.Command_AuthenticateJobManagerVerify, data, &wshrpc.RpcOpts{Route: ControlRootRoute})\n\t\tif err != nil {\n\t\t\tlog.Printf(\"wshrouter authenticate-jobmanager error linkid=%d jobid=%q: failed to verify job auth token: %v\", linkId, data.JobId, err)\n\t\t\treturn fmt.Errorf(\"failed to verify job auth token: %w\", err)\n\t\t}\n\t}\n\n\trouteId := MakeJobRouteId(data.JobId)\n\tlog.Printf(\"wshrouter authenticate-jobmanager success linkid=%d jobid=%q routeid=%q\", linkId, data.JobId, routeId)\n\timpl.Router.trustLink(linkId, LinkKind_Leaf)\n\timpl.Router.bindRoute(linkId, routeId, true)\n\n\treturn nil\n}\n\nfunc validateRpcContextFromAuth(newCtx *wshrpc.RpcContext) (string, error) {\n\tif newCtx == nil {\n\t\treturn \"\", fmt.Errorf(\"no context found in jwt token\")\n\t}\n\tif newCtx.IsRouter && newCtx.RouteId != \"\" {\n\t\treturn \"\", fmt.Errorf(\"invalid context, router cannot have a routeid\")\n\t}\n\tif newCtx.IsRouter && newCtx.ProcRoute {\n\t\treturn \"\", fmt.Errorf(\"invalid context, router cannot have a proc-route\")\n\t}\n\tif !newCtx.IsRouter && newCtx.RouteId == \"\" && !newCtx.ProcRoute {\n\t\treturn \"\", fmt.Errorf(\"invalid context, must have a routeid\")\n\t}\n\tif newCtx.IsRouter {\n\t\treturn \"\", nil\n\t}\n\treturn newCtx.GenerateRouteId(), nil\n}\n","sourceCodeStart":276,"sourceCodeEnd":310,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/wshutil/wshrouter_controlimpl.go#L276-L310","documentation":"validateRpcContextFromAuth inspects the RpcContext decoded from a JWT auth token and rejects it when the token contains no context at all. The JWT must embed an RpcContext describing the peer's route/role.","triggerScenarios":"Authenticating a connection with a JWT whose claims lack the RpcContext payload (nil after decoding).","commonSituations":"Token minted by an older/other client version that omitted the context claim; token manually crafted; claims dropped during token serialization.","solutions":["Regenerate the token so it includes the RpcContext claim","Check the token-minting code path (e.g. MakeClientAuthToken) embeds RpcContext","Upgrade mismatched client/server versions"],"exampleFix":"// before\ntoken := jwt Sign({\"sub\": routeId})\n// after\ntoken := jwt Sign({\"sub\": routeId, \"context\": wshrpc.RpcContext{RouteId: routeId}})","handlingStrategy":"validation","validationCode":"claims := decodeJwt(token)\nif claims[\"context\"] == nil {\n    return fmt.Errorf(\"token missing context claim\")\n}","typeGuard":"func hasRpcContext(claims map[string]any) bool {\n    ctx, ok := claims[\"context\"].(map[string]any)\n    return ok && ctx != nil\n}","tryCatchPattern":"routeId, err := validateRpcContextFromAuth(newCtx)\nif err != nil {\n    // regenerate token with embedded RpcContext before retrying\n}","preventionTips":["Always mint tokens via the shared helper that embeds RpcContext","Add a decode-time assertion on the context claim","Keep token schema versions aligned across client/server"],"tags":["wsh","jwt","authentication"],"backgroundTag":"jwt-missing-claim","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}