supabase / auth

A JWT based API for managing users and issuing JWT tokens
https://supabase.com/docs/guides/auth
MIT License
1.43k stars 346 forks source link

Sharing a Supabase Auth Session - Incompatibility Between Supabase Flutter and Swift SDKs: Session JSON Structure Mismatch #1727

Closed baptiste-veyrard closed 2 weeks ago

baptiste-veyrard commented 3 weeks ago

Bug report

Description:

I'm developing an application where I need to share a Supabase session between my main app, built using FlutterFlow (which uses the Supabase Flutter SDK), and an iOS extension target (a credential provider) where auth is built using the Supabase Swift SDK. I've encountered an issue where the session JSON structure used by the Supabase Flutter SDK is incompatible with what is expected by the Supabase Swift SDK. So it makes the JSON stored by the flutter SDK not parseable by the swift SDK.

JSON Structures:

  1. Supabase Swift SDK { "session": { "expires_in": 3290.1477999687195, "expires_at": 1724170095, "refresh_token": "6hgVKxl.......", "access_token": "eyJhbGciOiJIUzI1NiIsImtpZCI6ImwzY0JFV1dJZjBMQjZYTk0iLCJ0eXAiOiJKV1QifQ...", "user": { "email": "email@digdy.com", "user_metadata": { "sub": "7e852d69-XXXX-XXXX-XXXX-42b02d8edc85", "phone_verified": false, "email": "email@digdy.com", "email_verified": false }, ... }, "token_type": "bearer" }, "expiration_date": "2024-08-20T16:08:15.000Z" }

  2. Supabase Flutter SDK

{ "currentSession": { "access_token": "eyJhbGciOiJIUzI1NiIsImtpZCI6ImwzY0JFV1dJZjBMQjZYTk0iLCJ0eXAiOiJKV1QifQ...", "expires_in": 3600, "refresh_token": "6hgVKxl_Bkt537lJDCQkrw", "token_type": "bearer", "user": { "id": "7e852d69-e353-4852-8891-42b02d8edc85", "app_metadata": { "provider": "email", "providers": ["email"] }, ... } }, "expiresAt": 1724170095 }

To Reproduce

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

  1. Download the following test project from my GitHub ->[Supabase-Auth-test-project-Flutter-Swift](https://github.com/baptiste-veyrard/Supabase-Auth-test-project-Flutter-Swift) , where I already took a demo project and modify it, adding a keychain group and sharedGroup, leveraging FlutterSecureStorage to store supabase auth info...
  2. Open Xcode
  3. Add a developer team
  4. Add the Supabase swift SDK
  5. Add a supabase project URL and key -> in the main.dart file for flutter, in SupabaseAuthManager for swift
  6. Run "flutter pub get" and cd-> ios "pod install" in the terminal (I am using cocoapods)
  7. Build the "runner" target and login a user
  8. Build the "autofill-extension" target with Safari, go on a credential form (ex: signin facebook) and click on "password" the quicktype bar

Expected behavior

The Supabase SDKs for both Flutter and Swift should use a compatible JSON structure for session management, allowing seamless sharing of sessions between different platforms. Due to the print in the CredentialProviderViewController (in viewDidLoad) it should return a valid session.

Actual Behavior: The session JSON structure used by the Supabase Flutter SDK is incompatible with the structure expected by the Supabase Swift SDK, leading to decoding errors and the inability to share sessions across these platforms. The function init() in SupabaseAuthManager.swift return the following error: Failed to load session: keyNotFound(CodingKeys(stringValue: "session", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"session\", intValue: nil) (\"session\").", underlyingError: nil))

System information

More info: I cloned a swift package in order to add some prints in the package code to identify where the issue happened. It seems to happen in the "SessionStorage" file, at the line of code: return try storedData.flatMap { try AuthClient.Configuration.jsonDecoder.decode(StoredSession.self, from: $0).session }

As well, I tried to set my own JSONDecoder as it seems to be possible through the auth option of the supabase client. It was well initialized, but the SDK still uses the "original" JSONDecoder to perform the decoding tasks.

dshukertjr commented 2 weeks ago

Just leaving some info from the Flutter side here:

The supabase-flutter SDK stores the session in the following format.

  {
    "access_token": accessToken,
    "expires_in": expiresIn,
    "expires_at": expiresAt,
    "refresh_token": refreshToken,
    "token_type": tokenType,
    "provider_token": providerToken,
    "provider_refresh_token": providerRefreshToken,
    "user": {<user json>},
  }

supabase-flutter does allow developers to store the token in any location they want by defining a custom local storage, so as long as the shape of the session json match, sharing session between the Flutter and Swift SDK should be possible, but does the shape of the JSON structure match @grdsdev ?

baptiste-veyrard commented 2 weeks ago

Thanks for your answer. Indeed, I use a custom local storage, in the keychain via flutter_secure_storage. I well retrieve the data stored in it from my swift code, but it cannot be retrieved as a supabase session given the differences of the JSON values stored.

The JSON structures I provided in my post are the actual structures I retrieve when I print a session created in Swift and Flutter.

The format you provided is the session itself, in JSON, but when stored, it uses the following piece of code in session.dart (file of the supabase flutter package).

String get persistSessionString { final data = {'currentSession': toJson(), 'expiresAt': expiresAt}; return json.encode(data); }

And the Supabase Swift package is looking for the keyword "currentSession" and not "session". I succeed to make them matching in modifying the flutter sdk just for testing purpose, but modifying it is not a proper way to handle it for a long term perspective for a production app.

That is why then I tried to make my own JSON Decoder, but while well initialized, the supabase swift package does not use it to decode the retrieve session in the keychain unfortunatelly.

One workaround would be to modify the "data" value in a function before storing it via my custom local storage function, so without changing the package, but I thought it was a better idea to talk about it with the Supabase team first.

grdsdev commented 2 weeks ago

Hi @dshukertjr

Session JSON structure is compatible with Flutter, as it uses the same structure, the issue I see is that Flutter stores it in a currentSession key, along with a expiresAt. Swift uses the same approach, but stores in under a session key, and expirationDate, that is where the incompatibility happens.

If we match only those 2 keys, all should work as expected, without the need of custom decoding on Swift.

@baptiste-veyrard the reason for this not using your own JSON Decoder for decoding the session, is because I didn't want to expose the decoding/encoding of the internal stored session, and let you customize the storing/retrieving of raw Data through the custom local storage only.

Action items

  1. Rename keys to match on both Swift and Flutter; or
  2. Remove the wrapping keys, and store session directly.

@dshukertjr which one you prefer? (I prefer 2, as there is not need for the wrapping keys anymore)

dshukertjr commented 2 weeks ago

@baptiste-veyrard

The format you provided is the session itself, in JSON, but when stored, it uses the following piece of code in session.dart (file of the supabase flutter package).

That was the behavior of the v1 package, and it is no longer the behavior in the latest versions. Latest versions store the sessions in the format that I outlined up here. Please use the latest versions to verify the most up-to-date behavior.