rocksdanister/lively · error · UnauthorizedAccessException

Couldn't refresh tokens. You have to log in again

Error message

Couldn't refresh tokens. You have to log in again

What it means

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.

Source

Thrown at src/Lively/Lively.Gallery.Client/GalleryClient.cs:156

        public async Task<TokensModel> AuthenticateGithubAsync(string githubCode)
        {
            var message = new HttpRequestMessage(HttpMethod.Post, $"auth/google-token?code={githubCode}&provider=GITHUB");
            var result = await SendAsync<TokensModel>(message, false);

            var tokens = result.Data;
            _tokenStore.Set(tokens.AccessToken, tokens.RefreshToken, "GITHUB", tokens.Expiration);
            CurrentUser = await GetMeAsync();
            if(CurrentUser != null)
            {
                LoggedIn?.Invoke(this, EventArgs.Empty);
            }
            return result.Data;
        }

        private async Task<TokensModel> RefreshTokensAsync()
        {
            if (Tokens?.AccessToken == null || Tokens?.RefreshToken == null)
                throw new UnauthorizedAccessException("Couldn't refresh tokens. You have to log in again");
            var message = new HttpRequestMessage(HttpMethod.Post, "auth/refresh")
                .WithJsonContent(Tokens);
            var result = await SendAsync<TokensModel>(message, false, true);
            return result.Data;
        }

        public async Task<bool> LogoutAsync()
        {
            var message = new HttpRequestMessage(HttpMethod.Get, "auth/logout");
            var result = await SendAsync<object?>(message);
            CurrentUser = null;
            _tokenStore.Set(null, null, null, DateTime.MinValue);
            LoggedOut?.Invoke(this, EventArgs.Empty);
            return result.Success;
        }
        #endregion     
        #region Gallery
        public async Task DownloadWallpaperAsync(string id, string fileName, CancellationToken ct, Action<float, float, float> progressCallback = null)

View on GitHub (pinned to c1036feb66)

Solutions

  1. Prompt the user to log in again via the gallery login flow, then retry the original operation.
  2. Before any authed call, ensure the token store has both an access and refresh token; if not, route to login.
  3. Investigate token persistence if tokens repeatedly vanish across restarts.
  4. Catch UnauthorizedAccessException around gallery calls and surface a 'session expired, please sign in' message.

Example fix

// before
if (Tokens?.AccessToken == null || Tokens?.RefreshToken == null)
    throw new UnauthorizedAccessException("Couldn't refresh tokens. You have to log in again");

// after
if (Tokens?.AccessToken == null || Tokens?.RefreshToken == null)
{
    LoggedOut?.Invoke(this, EventArgs.Empty);
    throw new UnauthorizedAccessException("Session expired: please sign in again.");
}
Defensive patterns

Strategy: validation

Validate before calling

if (string.IsNullOrEmpty(client.Tokens?.AccessToken) || string.IsNullOrEmpty(client.Tokens?.RefreshToken))
{
    await NavigateToLogin();
    return;
}

Type guard

static bool HasRefreshableSession(GalleryClient c)
    => !string.IsNullOrEmpty(c.Tokens?.AccessToken)
       && !string.IsNullOrEmpty(c.Tokens?.RefreshToken);

Try / catch

try { await client.SomeAuthedCall(); }
catch (UnauthorizedAccessException ex) when (ex.Message.Contains("refresh tokens"))
{ await NavigateToLogin(); }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of rocksdanister/lively@c1036feb66 (2026-08-13). Data as JSON: /api/errors/df96030822d87619. Report an issue: GitHub.