{"record":{"id":"df96030822d87619","repo":"rocksdanister/lively","slug":"couldn-t-refresh-tokens-you-have-to-log-in-again","errorCode":null,"errorMessage":"Couldn't refresh tokens. You have to log in again","messagePattern":"Couldn't refresh tokens\\. You have to log in again","errorType":"exception","errorClass":"UnauthorizedAccessException","httpStatus":401,"severity":"error","filePath":"src/Lively/Lively.Gallery.Client/GalleryClient.cs","lineNumber":156,"sourceCode":"        public async Task<TokensModel> AuthenticateGithubAsync(string githubCode)\n        {\n            var message = new HttpRequestMessage(HttpMethod.Post, $\"auth/google-token?code={githubCode}&provider=GITHUB\");\n            var result = await SendAsync<TokensModel>(message, false);\n\n            var tokens = result.Data;\n            _tokenStore.Set(tokens.AccessToken, tokens.RefreshToken, \"GITHUB\", tokens.Expiration);\n            CurrentUser = await GetMeAsync();\n            if(CurrentUser != null)\n            {\n                LoggedIn?.Invoke(this, EventArgs.Empty);\n            }\n            return result.Data;\n        }\n\n        private async Task<TokensModel> RefreshTokensAsync()\n        {\n            if (Tokens?.AccessToken == null || Tokens?.RefreshToken == null)\n                throw new UnauthorizedAccessException(\"Couldn't refresh tokens. You have to log in again\");\n            var message = new HttpRequestMessage(HttpMethod.Post, \"auth/refresh\")\n                .WithJsonContent(Tokens);\n            var result = await SendAsync<TokensModel>(message, false, true);\n            return result.Data;\n        }\n\n        public async Task<bool> LogoutAsync()\n        {\n            var message = new HttpRequestMessage(HttpMethod.Get, \"auth/logout\");\n            var result = await SendAsync<object?>(message);\n            CurrentUser = null;\n            _tokenStore.Set(null, null, null, DateTime.MinValue);\n            LoggedOut?.Invoke(this, EventArgs.Empty);\n            return result.Success;\n        }\n        #endregion     \n        #region Gallery\n        public async Task DownloadWallpaperAsync(string id, string fileName, CancellationToken ct, Action<float, float, float> progressCallback = null)","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/rocksdanister/lively/blob/c1036feb664960722e34bf4309042c247d6a909d/src/Lively/Lively.Gallery.Client/GalleryClient.cs#L138-L174","documentation":"Thrown by GalleryClient.RefreshTokensAsync when the token store has no AccessToken or no RefreshToken. Refreshing requires both; absence means there is nothing to refresh from, so the user must re-authenticate. It is an UnauthorizedAccessException so existing auth-error handlers pick it up.","triggerScenarios":"RefreshTokensAsync() invoked when _tokenStore returns null/empty access or refresh token. Reached indirectly from InternalSendAsync when a request hits 401 and it tries to recover.","commonSituations":"User logged out but UI still issuing gallery calls; token persistence failed (file/credential-store write error) so a restart lost tokens; clock/token-store reset; first launch before any login.","solutions":["Prompt the user to log in again via the gallery login flow, then retry the original operation.","Before any authed call, ensure the token store has both an access and refresh token; if not, route to login.","Investigate token persistence if tokens repeatedly vanish across restarts.","Catch UnauthorizedAccessException around gallery calls and surface a 'session expired, please sign in' message."],"exampleFix":"// before\nif (Tokens?.AccessToken == null || Tokens?.RefreshToken == null)\n    throw new UnauthorizedAccessException(\"Couldn't refresh tokens. You have to log in again\");\n\n// after\nif (Tokens?.AccessToken == null || Tokens?.RefreshToken == null)\n{\n    LoggedOut?.Invoke(this, EventArgs.Empty);\n    throw new UnauthorizedAccessException(\"Session expired: please sign in again.\");\n}","handlingStrategy":"validation","validationCode":"if (string.IsNullOrEmpty(client.Tokens?.AccessToken) || string.IsNullOrEmpty(client.Tokens?.RefreshToken))\n{\n    await NavigateToLogin();\n    return;\n}","typeGuard":"static bool HasRefreshableSession(GalleryClient c)\n    => !string.IsNullOrEmpty(c.Tokens?.AccessToken)\n       && !string.IsNullOrEmpty(c.Tokens?.RefreshToken);","tryCatchPattern":"try { await client.SomeAuthedCall(); }\ncatch (UnauthorizedAccessException ex) when (ex.Message.Contains(\"refresh tokens\"))\n{ await NavigateToLogin(); }","preventionTips":["Check token-store presence before any authed operation.","Verify token persistence works across restarts.","Route all UnauthorizedAccessException from the gallery client through one login-handler."],"tags":["auth","tokens","network","gallery","oauth"],"backgroundTag":null,"analyzedSha":"c1036feb664960722e34bf4309042c247d6a909d","analyzedAt":"2026-08-13T13:03:12.648Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}