supabase-swift icon indicating copy to clipboard operation
supabase-swift copied to clipboard

`Auth.KeychainError(code=itemNotFound)` on MacOS with SDK v2.16

Open rebryk opened this issue 1 year ago • 14 comments

Bug report

  • [+] I confirm this is a bug with Supabase, not with my own application.
  • [+] I confirm I have searched the Docs, GitHub Discussions, and Discord.

Describe the bug

When i switched from supabase v2.0 to v2.16 try await supabaseClient.auth.session throws Auth.KeychainError(code=itemNotFound) on MacOS.

To Reproduce

  1. Create a sign URL
try supabaseClient.auth.getOAuthSignInURL(
      provider: Provider.google,
      redirectTo: `app-schema://auth`
)
  1. Open url
NSWorkspace.shared.open(url)
  1. Handle deeplink after Google authorization
func application(_ application: NSApplication, open urls: [URL]) {
    if let url = urls.first, url.host == "auth" {
        Task {
            try await supabaseClient.auth.session(from: url)
        }
    }
}
  1. Step 3 returns a valid session; however, try await supabaseClient.auth.session will throw an Auth.KeychainError(code=itemNotFound) error.

Expected behavior

I expect try await supabaseClient.auth.session to restore the session from the keychain as it did before in previous SDK versions.

System information

  • OS: macOS
  • Browser (if applies) Arc Browser
  • Version of supabase: v2.16

rebryk avatar Aug 28 '24 06:08 rebryk

Hi @rebryk try attaching a logger on Supabase initialization, and let me know if you see any other log being fired, this is how you can do it.

let supabase = SupabaseClient(
  supabaseURL: URL(string: "URL")!,
  supabaseKey: "API_KEY",
  options: .init(
    global: .init(logger: AppLogger())
  )
)

struct AppLogger: SupabaseLogger {
  let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "supabase")

  func log(message: SupabaseLogMessage) {
    switch message.level {
    case .verbose:
      logger.log(level: .info, "\(message.description)")
    case .debug:
      logger.log(level: .debug, "\(message.description)")
    case .warning, .error:
      logger.log(level: .error, "\(message.description)")
    }
  }
}

grdsdev avatar Aug 28 '24 14:08 grdsdev

@grdsdev

2024-08-28T08:55:35-0700 info supabase : [n/a] 2024-08-28T15:55:35Z [verbose] [Helpers] [Helpers/LoggerInterceptor.intercept(_:next:):23] Request: POST https://<URL>/auth/v1/token
2024-08-28T08:55:35-0700 info supabase : [n/a] 2024-08-28T15:55:35Z [verbose] [Helpers] [Helpers/LoggerInterceptor.intercept(_:next:):33] Response: Status code: 200 Content-Length: 3413
2024-08-28T08:55:35-0700 error supabase : [n/a] 2024-08-28T15:55:35Z [error] [Auth] [Auth/SessionManager.update(_:):103] Failed to store session: Unspecified Keychain error: -34018.
2024-08-28T08:55:35-0700 debug supabase : [n/a] 2024-08-28T15:55:35Z [debug] [Auth] [Auth/SessionManager.session():41] begin
2024-08-28T08:55:35-0700 debug supabase : [n/a] 2024-08-28T15:55:35Z [debug] [Auth] [Auth/SessionManager.session():41] error: errSecItemNotFound: The item cannot be found.
2024-08-28T08:55:35-0700 debug supabase : [n/a] 2024-08-28T15:55:35Z [debug] [Auth] [Auth/SessionManager.session():41] end

rebryk avatar Aug 28 '24 15:08 rebryk

The key is Failed to store session: Unspecified Keychain error: -34018. That error is errSecMissingEntitlement, it means there is something wrong with the added entitlement, can you check this https://developer.apple.com/documentation/security/errsecmissingentitlement and verify if your project is correctly configured?

I'll also check on my side, if there is anything wrong, since you mentioned it stoped working.

grdsdev avatar Aug 28 '24 17:08 grdsdev

As far as I understand, macOS apps have default access to a private Keychain for storing app-specific data (you do not need to add any entitlements, i don't use keychain sharing). Also, everything works with previous versions of the SDK.

@grdsdev so it is most likely an issue with the new version

Let me know if i can help

rebryk avatar Aug 28 '24 19:08 rebryk

It seems it started after this https://github.com/supabase/supabase-swift/pull/455

grdsdev avatar Aug 28 '24 19:08 grdsdev

you are right: 2.14 works, and 2.14.1 does not

rebryk avatar Aug 28 '24 21:08 rebryk

Doing a few tests and if you add the keychain sharing capability to your app, even if you don't assign any access group, it works, will try to find on docs why that happens.

grdsdev avatar Aug 28 '24 21:08 grdsdev

Yeah, when I add the keychain sharing capability, the app works. Hopefully it will not break anythings else

rebryk avatar Aug 29 '24 07:08 rebryk

I have an issue when I run a signed release with empty keychain sharing capability: app.getfluently.Fluently: Unsatisfied entitlements: keychain-access-groups

rebryk avatar Aug 29 '24 07:08 rebryk

Same thing when I add:

<key>keychain-access-groups</key>
<array>
    <string>$(AppIdentifierPrefix)app.getfluently.Fluently</string>
</array>

rebryk avatar Aug 29 '24 08:08 rebryk

@rebryk if you add an access group, then you need to provide a custom KeychainLocalStorage implementation with the access group.

grdsdev avatar Aug 30 '24 17:08 grdsdev

@grdsdev do you have an off-the-shelf implementation? i just want to use supabase in a way I used it before

rebryk avatar Aug 30 '24 22:08 rebryk

You can use the following code, once I merge this https://github.com/supabase/supabase-swift/pull/519

KeychainLocalStorage is the default implementation used internally, and it is exposed so you can customize it.

let supabase = SupabaseClient(
  supabaseURL: URL(string: "https://project-id.supabase.com")!,
  supabaseKey: "supabase-anon-key",
  options: .init(
    auth: .init(
      localStorage: KeychainLocalStorage(
        accessGroup: "<# Your Team ID #>.app.getfluently.Fluently"
      )
    )
  )
)

grdsdev avatar Sep 02 '24 11:09 grdsdev

@grdsdev i have an issue with keychain-access-groups capability, so i would probably stick to v2.14.0

  1. I add capability
	<key>keychain-access-groups</key>
	<array>
		<string>$(AppIdentifierPrefix)app.getfluently.Fluently</string>
	</array>
  1. Sign the build
codesign \
    --force \
    --deep \
    --entitlements "$ENTITLEMENTS_FILE" \
    --options runtime \
    --sign $devTeamID \
    "Builds/$BUILD_TYPE/root/${APP_NAME}.app"
  1. Get an error when open the signed app app.getfluently.Fluently: Unsatisfied entitlements: keychain-access-groups

rebryk avatar Sep 02 '24 22:09 rebryk

@grdsdev

2024-08-28T08:55:35-0700 info supabase : [n/a] 2024-08-28T15:55:35Z [verbose] [Helpers] [Helpers/LoggerInterceptor.intercept(_:next:):23] Request: POST https://<URL>/auth/v1/token
2024-08-28T08:55:35-0700 info supabase : [n/a] 2024-08-28T15:55:35Z [verbose] [Helpers] [Helpers/LoggerInterceptor.intercept(_:next:):33] Response: Status code: 200 Content-Length: 3413
2024-08-28T08:55:35-0700 error supabase : [n/a] 2024-08-28T15:55:35Z [error] [Auth] [Auth/SessionManager.update(_:):103] Failed to store session: Unspecified Keychain error: -34018.
2024-08-28T08:55:35-0700 debug supabase : [n/a] 2024-08-28T15:55:35Z [debug] [Auth] [Auth/SessionManager.session():41] begin
2024-08-28T08:55:35-0700 debug supabase : [n/a] 2024-08-28T15:55:35Z [debug] [Auth] [Auth/SessionManager.session():41] error: errSecItemNotFound: The item cannot be found.
2024-08-28T08:55:35-0700 debug supabase : [n/a] 2024-08-28T15:55:35Z [debug] [Auth] [Auth/SessionManager.session():41] end

I have the same issue ! But it happens when i try to sing in with Apple, and thats my code:

var supabase = SupabaseClient(
        supabaseURL: URL(string:AppConstants.supabaseURLString)!,
        supabaseKey: AppConstants.supabaseKey,
        options: SupabaseClientOptions(
            auth: .init(storage: KeychainLocalStorage(
                    service: "<# service #>",
                    accessGroup: "<# teamid.com.app.identifier #>"
                  )
                ),
            global: SupabaseClientOptions.GlobalOptions(
                headers: [
                    "rs-user-id": UserStore.shared.userId ?? ""
                ],
                logger: Logger()
            )
        )
    )
Task {
            do {
                guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential else {
                    return
                }
                
                guard let idToken = credential.identityToken
                    .flatMap({ String(data: $0, encoding: .utf8) }) else {
                    return
                }
                
                try await RemoteClient.shared.supabase.auth.signInWithIdToken(
                    credentials: .init(
                        provider: .apple,
                        idToken: idToken
                    )
                )
                print("Apple login success!")
            } catch {
                dump(error)
            }
        }

image Can you help me, thanks! @grdsdev

xiaowei-dreame avatar Oct 12 '24 07:10 xiaowei-dreame