supabase-community / supabase-csharp

A C# Client library for Supabase
https://github.com/supabase-community/supabase-csharp/wiki
MIT License
485 stars 50 forks source link

Postgrest.Exceptions.PostgrestException: {"code":"PGRST301","details":null,"hint":null,"message":"JWT expired"} #150

Closed davidchieregato closed 4 months ago

davidchieregato commented 5 months ago

Bug report

Describe the bug

The bug is quite difficult to reproduce because it appears rarely when resuming the application. I am using .net MAUI and the Exception is thrown to the application Main in native code. This exception will make the app crash but the autorenew of the token should be managed by the supabase-csharp library.

To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:

I have a singleton class in MauiProgram.cs

var options = new SupabaseOptions
        {
            AutoRefreshToken = true,
            AutoConnectRealtime = true,
            SessionHandler = new CustomSessionHandler()
        };
builder.Services.AddSingleton(provider => new Supabase.Client(url, key, options));

My CustomSessionHandler is like this:

using Supabase.Gotrue.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Supabase.Gotrue;
using Newtonsoft.Json;
using System.IO;

namespace mater;

public class CustomSessionHandler : IGotrueSessionPersistence<Session>
{
    public void SaveSession(Session session)
    {
        var cacheFileName = ".gotrue.cache";

        try
        {
            var cacheDir = FileSystem.CacheDirectory;
            var path = Path.Join(cacheDir, cacheFileName);
            var str = JsonConvert.SerializeObject(session);

            using (StreamWriter file = new StreamWriter(path))
            {
                file.Write(str);
                file.Dispose();
            };
            Console.WriteLine("!--------------SAVED SESSION--------------!");
        }
        catch (Exception err)
        {
            Console.WriteLine("Unable to write cache file." + err);
        }

    }

    public void DestroySession()
    {
        // Destroy Session on Filesystem or in browser storage
        var cacheFileName = ".gotrue.cache";
        var cacheDir = FileSystem.CacheDirectory;
        var path = Path.Join(cacheDir, cacheFileName);
        if (File.Exists(path))
        {
            File.Delete(path);
        }
        //Other logic Delete cache

        Console.WriteLine("!--------------DESTROY SESSION--------------!");
    }

    public Session LoadSession()
    {
        try
        {
            var cacheFileName = ".gotrue.cache";
            var cacheDir = FileSystem.CacheDirectory;
            var path = Path.Join(cacheDir, cacheFileName);
            using StreamReader r = new(path);
            string json = r.ReadToEnd();
            // Retrieve Session from Filesystem or from browser storage
            Console.WriteLine("!--------------LOAD SESSION--------------!");
            return JsonConvert.DeserializeObject<Session>(json);
        }
        catch (Exception err)
        {

            Console.WriteLine("Unable to write cache file." + err);
            return null;
        }
    }
}

And I retrieve the service in the constructor in multiple places like this public Utils(IServiceProvider service) { _supabaseClient = service.GetRequiredService(); }

Expected behavior

The library should renew the auth token.

Screenshots

NA

System information

Additional context

The stacktrace is as follows

Postgrest.Exceptions.PostgrestException: {"code":"PGRST301","details":null,"hint":null,"message":"JWT expired"}
  at at Postgrest.Helpers.MakeRequest(ClientOptions clientOptions, HttpMethod method, String url, JsonSerializerSettings serializerSettings, Object data, Dictionary`2 headers, CancellationToken cancellationToken)
  at at Postgrest.Helpers.<MakeRequest>d__2`1[[mater.Models.SupabaseSections, mater, Version=1.0.7.0, Culture=neutral, PublicKeyToken=null]].MoveNext()
  at at Postgrest.Table`1.<Single>d__65[[mater.Models.SupabaseSections, mater, Version=1.0.7.0, Culture=neutral, PublicKeyToken=null]].MoveNext()
  at mater.Auth.ApiSections.GetExplored() in /Users/davidchieregato/Projects/mater/mater/Auth/ApiSections.cs:30
  at mater.Auth.ApiSections.SetSectionAsVisited(String Section) in /Users/davidchieregato/Projects/mater/mater/Auth/ApiSections.cs:92
  at mater.Views.AttFisica.SetVisitedSection() in /Users/davidchieregato/Projects/mater/mater/Views/Lifestyle/AttFisica.xaml.cs:22
  ...
  at UIKit.UIApplication.UIApplicationMain(Int32 argc, String[] argv, IntPtr principalClassName, IntPtr delegateClassName) in /Users/builder/azdo/_work/1/s/xamarin-macios/src/UIKit/UIApplication.cs:60
  at UIKit.UIApplication.Main(String[] args, Type principalClass, Type delegateClass) in /Users/builder/azdo/_work/1/s/xamarin-macios/src/UIKit/UIApplication.cs:94
  at mater.Program.Main(String[] args) in /Users/davidchieregato/Projects/mater/mater/Platforms/iOS/Program.cs:13
acupofjose commented 5 months ago

Morning @davidchieregato! Thanks for the report.

My initial reaction is that the application must be in the background during the normal interval that the gotrue-csharp client would normally refresh the token. The client itself does manage token refresh but it does so using a system timer. The thought being to make the token refresh as general as possible so-as to not be locked into a framework specific implementation. So, what I think is happening is that the timer is never being called to refresh the token because the application is in a suspended state.

What I would do in your case would be to check the gotrue token in your app resume lifecycle event and call Auth.RefreshSession() or Auth.RefreshToken(). This may be something you'd want to do periodically in the background too. You may also want to re-evaluate your token lifetime (set in the Supabase Admin panel).

Thoughts?

davidchieregato commented 5 months ago

It's possible that the issue is related to how timers are managed when in background.

I just tried adding a resume event in my Application. I will report back if the issue arises or not within a few days, thanks for the hint!

protected override void OnResume()
    {
        base.OnResume();
        Sup.Auth.RefreshSession();
    }