Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Link apple identity natively #588

Open
MilesV64 opened this issue Nov 4, 2024 · 4 comments
Open

Link apple identity natively #588

MilesV64 opened this issue Nov 4, 2024 · 4 comments
Labels
enhancement New feature or request

Comments

@MilesV64
Copy link

MilesV64 commented Nov 4, 2024

Feature Request

We have native apple sign in, but no way (that I can see) of natively linking Apple as a sign in option to an existing account, we need to use the OAuth flow which redirects to web which is not a natural experience on an apple device for authenticating with apple.

Is it possible to skip the PKCE flow flor linking apple as an identity, authenticating natively?

@MilesV64 MilesV64 added the bug Something isn't working label Nov 4, 2024
@grdsdev
Copy link
Collaborator

grdsdev commented Nov 5, 2024

Hi @MilesV64, unfortunately, that isn't supported yet. The only way to link an account is through the OAuth web flow.

We don't have an ETA for this, but it is on the roadmap, I'll keep this issue open for posting updates for this feature.

Thanks.

@grdsdev grdsdev added enhancement New feature or request and removed bug Something isn't working labels Nov 5, 2024
@grdsdev grdsdev pinned this issue Nov 25, 2024
@RicoMontulet
Copy link

RicoMontulet commented Nov 27, 2024

Hi @MilesV64, I ran into a similar issue with my app. Users can sign in anonymously, but at some point they want to create an account and link all their contributions to this new account.

I was thinking about the following work-around without needed an OAuth web flow. It's far from perfect.. A native solution would be better but it doesn't exist yet... There will be issues, please let me know so I can try and resolve them!

One security issue is obvious: I can't verify that the old uuid actually belonged to this user since functions aren't stateful and I can only call it after the user has signed in with apple (or OAuth, ...) since I need to know their new uuid. If there are solutions to this please let me know!

My solution that seems to work in test environment:

I create a function in Supabase to merge their Profile (table with some more info about auth.users. Profiles has a uuid 'user_id' which is a foreign key into auth.users.id, and a uuid 'profile_id' which is used in all other tables as foreign key (when a user deletes their account I don't want to cascade and delete all their contributions, instead I anonymize them). Anyways back to the function below. I call this function from the users device when they successfully auth with apple and pass their auth.user.id from the anonymous user and from the authenticated user.

CREATE OR REPLACE FUNCTION public.merge_user_requested(old_uuid UUID, new_uuid UUID) 
RETURNS VOID 
SET search_path = ''
LANGUAGE plpgsql 
SECURITY DEFINER 
AS $$
DECLARE
    old_profile RECORD;
    caller_user UUID := COALESCE(auth.uid(), gen_random_uuid()); -- Get the caller's user ID or fallback to a random UUID
    old_user_is_anonymous BOOLEAN;
    old_profile_id UUID;
    new_profile_id UUID;
BEGIN
  -- Check if the user calling the function is the same as new_uuid
  IF caller_user IS DISTINCT FROM new_uuid THEN
    RAISE EXCEPTION 'Permission denied: You can only merge your own profiles.';
  END IF;

  -- Check if old_uuid is an anonymous account
  SELECT is_anonymous INTO old_user_is_anonymous 
  FROM auth.users 
  WHERE id = old_uuid;

  IF old_user_is_anonymous IS DISTINCT FROM TRUE THEN
    RAISE EXCEPTION 'The old account must be an anonymous account to proceed with the merge.';
  END IF;

  -- Get the profile_id of the old_uuid
  SELECT profile_id INTO old_profile_id
  FROM profiles
  WHERE user_id = old_uuid;

  -- Get the profile_id of the new_uuid
  SELECT profile_id INTO new_profile_id
  FROM profiles
  WHERE user_id = new_uuid;

  -- Delete the unused new profile
  DELETE FROM profiles WHERE user_id = new_uuid;

  -- Update the old profile with the new auth.user.id
  UPDATE profiles
  SET
      user_id = new_uuid,
      updated_at = NOW()
  WHERE user_id = old_uuid;

  -- Delete the old user from the auth.users table since it is a waste of space.
  DELETE FROM auth.users WHERE id = old_uuid;

END;
$$;
func linkAnonymousAccountToApple(result: Result<ASAuthorization, Error>) async throws {
        isLoading = true
        defer { isLoading = false }
        
        guard let credential: ASAuthorizationAppleIDCredential = try result.get().credential as? ASAuthorizationAppleIDCredential
        else {
            // Handle error
            return
        }
        
        guard let idToken = credential.identityToken
            .flatMap({ String(data: $0, encoding: .utf8) })
        else {
            // handle error
            return
        }
        // Save the anonymous (old) user id
        let old_uuid = user?.id
        
        let session = try await SupabaseModel.shared.client.auth.signInWithIdToken(
            credentials: .init(
                provider: .apple,
                idToken: idToken
            )
        )
        
        user = session.user
        
        // Unpack the old and new uuids and call the function using RPC
        if let oldUUID = old_uuid, let newUUID = user?.id {
            // Call the RPC with correctly named parameters
            let params: [String: UUID] = [
                "old_uuid": oldUUID,
                "new_uuid": newUUID
            ]
            
            // Add error handling here.
            let _ = try await SupabaseModel.shared.client.rpc("merge_user_requested", params: params).execute()
        }

@paul-brenner
Copy link

I'm currently stuck without this. Where can I learn about the "OAuth flow which redirects to web" approach?

@MilesV64
Copy link
Author

@paul-brenner

Using the swift client you can call (passing in your own redirect url ie your-domain.com/provider/apple:

try await supabaseClient.auth.linkIdentity(
    provider: .apple,
    redirectTo: redirect,
    launchURL: launchURL
)

Then launchURL is a closure and in your app you can launch a WKWebView and set the WKNavigationDelegate, then in func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) you check for the redirect url you passed and then get the session from the url via the supabase auth client again: try await supabaseClient.auth.session(from: url)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants