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

Support Sign in With Apple #4

Closed
catlan opened this issue Aug 13, 2021 · 18 comments
Closed

Support Sign in With Apple #4

catlan opened this issue Aug 13, 2021 · 18 comments
Labels
enhancement New feature or request

Comments

@catlan
Copy link

catlan commented Aug 13, 2021

Feature request

Following on the thread in supabase/supabase#1882 and supabase/supabase#2805 I looked into adding support for AuthenticationServices and in this regard ASAuthorizationAppleIDCredential.

Is your feature request related to a problem? Please describe.

When GoTrue signs users in using SIWA they're redirected to the OAuth apple site where they sign in and eventually GoTrue received the above parameters, just through the OAuth callback flow. If this flow were to be used on iOS, it would be a very clunky UX, and could be grounds for an iOS app to be rejected from review.

Describe the solution you'd like

I started work on this here: https://github.com/catlan/gotrue-swift/commit/bac99073ad56ce6fa3257f33a22a628a135e0e9a

The problem is that the response I get:
"Invalid token: signing method RS256 is invalid"

I'm not too familiar with either Sign in With Apple nor supabase. Any clues on what is required to make this work?

@catlan catlan added the enhancement New feature or request label Aug 13, 2021
@thecoolwinter
Copy link
Contributor

thecoolwinter commented Aug 13, 2021

In a normal SIWA token exchange, the app sends the access token and identity token to the server. Then, the server validates the identity token and stores the access token for later use. Then, instead of using the Apple JWT, you're supposed to sign and send back a custom JWT and access token for your own server. Apple's JWTs only last 24 hours, so it's built to be like that. Then if the server needs to update or request user info it uses the access token to ask Apple for that info. The SIWA JWT is only used for the original user sign in.

Right now I think the GoTrue server would need to be modified to accept and validate the JWT from the AuthenticationServices framework and send back a GoTrue JWT.

I was also looking at the /callback endpoint and wondering if we could send the access token and identity token to that endpoint and if it would work.

@catlan
Copy link
Author

catlan commented Aug 19, 2021

So do we need to move the issue to server component?

@thecoolwinter
Copy link
Contributor

Yeah I think we do, once they add an endpoint we can add it to the swift repo. We should also test using the existing /callback endpoint to see if that does in fact work

@thecoolwinter
Copy link
Contributor

Actually scratch that, we'd need support for sending a nonce with the tokens, so the /callback endpoint won't work.

Sadly I don't have enough experience in Go to make sense of the server library

I'll make an issue there and reference this.

@johndpope
Copy link

supabase/auth#189
It seems this callback with nonce has been delivered -
I'm looking to port existing java architecture over to supabase - but without this - it's a bit of a hinderance.

@wmorgue
Copy link

wmorgue commented Nov 7, 2022

Any updates?

@mergesort
Copy link

I had the same question. I'm starting on an app that supports Sign In With Apple and only now discovered that SIWA isn't supported. Not meaning to pressure y'all but is there an estimate on when SIWA could work?

@johndpope
Copy link

johndpope commented Nov 13, 2022

I’ve done apple stuff before a few times - perhaps we could compare notes and make a self hosted nodejs apple sign in / express passport with JavaScript that updates the auth.users table instead of being blocked here. It’s a bit crap but need to ship the app. Note - a lot of the javascript is specific to browser stuff / my approach is using nodejs below and it does not have a user / session.

The critical distinction in flows - native login on server can use grant_type authorization_code (this is different to web callback url / access_tokens that webflow uses)

SERVER - nodejs on ec2 - (ideally this would be supported on supabase)

	const params = {
		grant_type: 'authorization_code', // refresh_token authorization_code
		code: req.body.code,
		redirect_uri: config.apple.redirectURI,
		client_id: config.apple.clientID,
		client_secret: clientSecret,
		// refresh_token:req.body.id_token
	}
		method: 'POST',
		headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
		data: qs.stringify(params),
		url: 'https://appleid.apple.com/auth/token'

-> here we get back an authorization.code - on swift app (see sample repo below)

   if let code = appleCredential.authorizationCode {
            let authCode = String(data: code, encoding: .utf8)!
            loginWithApple( authCode) // send this code to our nodejs -> create the user / or login / return supabase jwt.
        }

in essense some psuedocode

STEP 1:
from iphone - we will send the apple authorization code we get back to our hosted ec2 server running this code / p8 file

curl -X POST -d 'code=cf2add9a5a15842d4b06683fa89152446.0.ntrx.OHzVN63UPWqSjEr-oBsU6g' http://0.0.0.0:80/login/apple
{"message":"error","error":{"error":"invalid_grant","error_description":"The code has expired or has been revoked."}}%


A) if no account exists on system (google / firebase / facebook / apple / snapchat) - create a new one

const returnExistingSupabaseJWTorCreateAccount = async (jwtClaims) => {

	let user =  await findExistingUserByEmail(jwtClaims.email);
	{
		const { data: response, error } = await supabase.auth.admin.listUsers();
		// console.log("listUsers response:", response.users);
		for await (let u of response.users) {
			if (u.id == user.id) {
				console.log("we found existing user in supabase:", u);
			}
		}
	}

	if (user == null) {
		console.log("🌱 creating user");
		const { data: newUser, error } = await supabase.auth.admin.createUser({
			email: jwtClaims.email,
			email_confirm: true // missing provide / identities guff
		})
		console.log("newUser:", newUser);

	} else {
		console.log("🌱 we found a gotrue user:",user);
		// create an access token - this needs more work - or port to golang 
		let claims = {
			"StandardClaims": {
				"sub": user.id,
				"aud": "",
				"exp": Math.floor(Date.now() / 1000),
			},
			"Email": user.Email,
			"AppMetaData": user.AppMetaData,
			"UserMetaData": user.UserMetaData,
		}
		console.log("✅ claims:", claims);
		const jwt = sign(claims, config.supabase.jwtSecret); // needs testing
		console.log("jwt:", jwt);
		return jwt;
	}

}

additional notes - supabase/auth#451

UPDATE:
so instead of using the official javascript gotrue libraries -
we're can use postgres to get to auth.users (auth schema is not public)

I've made sample project ( follow along here)
https://github.com/johndpope/Sign-in-with-Apple-for-supabase

This needs more work -
blocked on this - how to craft the access token / jwt for the user found.
It would be better if this logic lived in gotrue - supabase/auth#807

netlify/gotrue-js#114

Once authorization code is verified - we can dig up the the apple jwt claims (though we may need to pass apple email for email)

const jwtClaims = { iss: 'https://appleid.apple.com',
			  aud: 'app.test.ios', 
			  exp: 1579483805,
			  iat: 1579483205,
			  sub: '000317.c7d501c4f43c4a40ac3f79e122336fcf.0952',
			  at_hash: 'G413OYB2Ai7UY5GtiuG68A',
			  email: '[email protected]',
			  email_verified: 'true',
			  is_private_email: 'true',
			  auth_time: 1579483204 }

here we get a response from apple (jwtClaims) - and return a jwt(supabase) referening auth.user
Screenshot 2022-11-14 at 8 51 32 pm

Screenshot 2022-11-14 at 8 50 35 pm

code in my sample repo uses the supabase.admin.createuser -
but I can drop into postgres -> auth.users and manually create -
though I'm not clear on what to put for password.

supabase/supabase#5248

INSERT INTO
  auth.users (
    id,
    instance_id,
    ROLE,
    aud,
    email,
    raw_app_meta_data,
    raw_user_meta_data,
    is_super_admin,
    encrypted_password,
    created_at,
    updated_at,
    last_sign_in_at,
    email_confirmed_at,
    confirmation_sent_at,
    confirmation_token,
    recovery_token,
    email_change_token_new,
    email_change
  )
VALUES
  (
    gen_random_uuid(),
    '00000000-0000-0000-0000-000000000000',
    'authenticated',
    'authenticated',
    '[email protected]',
    '{"provider":"email","providers":["email"]}',
    '{}',
    FALSE,
    crypt('Pa55word!', gen_salt('bf')),
    NOW(),
    NOW(),
    NOW(),
    NOW(),
    NOW(),
    '',
    '',
    '',
    ''
  );

INSERT INTO
  auth.identities (
    id,
    provider,
    user_id,
    identity_data,
    last_sign_in_at,
    created_at,
    updated_at
  )
VALUES
  (
    (
      SELECT
        id
      FROM
        auth.users
      WHERE
        email = '[email protected]'
    ),
    'email',
    (
      SELECT
        id
      FROM
        auth.users
      WHERE
        email = '[email protected]'
    ),
    json_build_object(
      'sub',
      (
        SELECT
          id
        FROM
          auth.users
        WHERE
          email = '[email protected]'
      )
    ),
    NOW(),
    NOW(),
    NOW()
  );

N.B. - I have the webflow sign in working using client_secret.rb in my repo ( this is not what I want)
Screenshot 2022-11-15 at 1 13 53 am

this is apple's latest sample sign in code - can download here
https://developer.apple.com/documentation/authenticationservices/implementing_user_authentication_with_sign_in_with_apple

Screenshot 2022-11-15 at 1 35 04 am

Screenshot 2022-11-15 at 1 37 24 am

Screenshot 2022-11-15 at 1 36 55 am

UPDATE:
so critically - we get back authorization.code in the response from apple on successful logins.
Screenshot 2022-11-15 at 1 45 14 am

we can use then post this to the nodejs server - and get the supabase jwt.
Screenshot 2022-11-15 at 2 18 57 am

Problem I have is this isn't a legitimate jwt.
I've pushed. all the code + included apple sample sign in - I need some backend help to get this across the line.
https://github.com/johndpope/Sign-in-with-Apple-for-supabase
specifically - this jwt is not complete - it's been hacked in the server.js

Screenshot 2022-11-15 at 2 23 07 am

NOTEs - when using the client_secret - webflow - the identity is correctly stored using the gotrue flows.

I need to either get gotrue to support this authorization flow upstream - or we need craft this to insert

Screenshot 2022-11-15 at 2 31 38 am

```javascript

// I need to
let identityData = {"iss":"https://appleid.apple.com/auth/keys","sub":"000265.c7232b56014b4437adec51370b3d7004.1422","name":"John Pope","email":"[email protected]","full_name":"John Pope","provider_id":"000265.c7232b56014b4437adec51370b3d7004.1422","email_verified":true}

const res2 = await pool.query('INSERT INTO auth.identities ( id, provider, user_id, identity_data, last_sign_in_at, created_at, updated_at ) VALUES ( $1::UUID, \'email\', $1::UUID, json_build_object( \'sub\', $1::UUID ), NOW(), NOW(), NOW() );', [result.id]);
			



@matthewmorek
Copy link

matthewmorek commented Dec 20, 2022

Any update on having this fixed to work with "Sign in with Apple" workflow, or is this still a partially server-side issue that needs a broader look?

@wweevv-johndpope
Copy link

I'm wondering if this is a more complete fit - https://next-auth.js.org/adapters/supabase = still need the nodejs server - but using this adapter - maybe more elegant than hacking with postgres injection....

@arguiot
Copy link

arguiot commented Mar 19, 2023

Hello any updates? This issue has been opened in 2021... any ETA on when this would be fixed?

@ajsharp
Copy link

ajsharp commented Apr 2, 2023

Bumping this. Any updates from the supabase team? cc @thecoolwinter

@johndpope
Copy link

@kangmingtay - is there anyway we can progress this?

@kangmingtay
Copy link
Contributor

Hi @johndpope, apologies for the late reply, this should be possible via the signInWithIdToken method

You'll need to add your IOS Bundle ID in the dashboard's auth settings under the apple provider -> "Services ID". We're looking to add the "IOS Bundle ID" as a field in the dashboard as well so that we can support both mobile & web logins through apple.

As this is a pretty long thread, i just wanted to make sure i'm understanding the issue correctly:

We want to be able to use native apple authentication with swift for SIWA. Using something like Apple Authentication Services would be ideal for the iOS App Store reviews and also provides a nice native experience for the user rather than redirecting them to a web browser if you use signInWithOAuth. There needs to be some way to pass in the id token returned from SIWA and a nonce to gotrue to complete the authentication flow with Supabase.

@johndpope
Copy link

johndpope commented Apr 4, 2023

at first glance - it doesn't seem adequate -
we have the official sign in via apple code in swift provided by apple - it's the orange screen shots - juice app above.

I want this app to sign into supabase - using swift - natively.

the successful authorization code we get back from apple looks like this
'code=cf2add9a5a15842d4b06683fa89152446.0.ntrx.OHzVN63UPWqSjEr-oBsU6g'

it's not a json web token. I can see it's possible to perhaps craft this to fit your suggestion - but...

export type SignInWithIdTokenCredentials = {
  /**
   * Only Apple and Google ID tokens are supported for use from within iOS or Android applications.
   */
  provider: 'google' | 'apple'
  /** ID token issued by Apple or Google. */
  token: string
  /** If the ID token contains a `nonce`, then the hash of this value is compared to the value in the ID token. */
  nonce?: string
  options?: {
    /** Verification token received when the user completes the captcha on the site. */
    captchaToken?: string
  }
}


when I hit the backend in javascript / passport / node for my approach -
it runs some extra validation against apple servers to verify code

this is apple / nodejs passport sample node
https://github.com/johndpope/Sign-in-with-Apple-for-supabase/blob/8da249ef1311353e5501930a7519a61bb5808e8e/server.js#L11

when I search supbase codebase for
response_type=code OR https://appleid.apple.com/auth/authorize
there's no hits. meaning this functionality doesn't exist?

this is the heart of how the backend - gotrue should go outbound with above authorization code - and either create user / or login and return the jwt. please help - I want this fixed. If you can take the above sample apple code - and hack to use the token approach - I'm also call with that.

// BACKEND - APPLE LOGIN 
// https://developer.apple.com/documentation/signinwithapplerestapi/generate_and_validate_tokens
//  const url = new URL(`https://appleid.apple.com/auth/authorize?scope=name%20email&client_id=${appleid.client_id}&redirect_uri=${redirectUri}&response_type=code%20id_token&response_mode=form_post`)

const getAuthorizationUrl = () => {
	const url = new URL('https://appleid.apple.com/auth/authorize');
	url.searchParams.append('response_type', 'code id_token');
	url.searchParams.append('response_mode', 'form_post');
	url.searchParams.append('client_id', config.apple.clientID);
	url.searchParams.append('redirect_uri', config.apple.redirectURI);
	url.searchParams.append('scope', 'name,email');
	return url.toString();
};

UPDATE
it seems no one has ever used this call signInWithIdToken in swift? presumably it's not going to work?
this function lives in js library - and we need to be hitting a golang / gotrue end point instead?
this is why where asking for native support - not through webpage with sign in via apple (which works).

Screenshot 2023-04-04 at 2 12 02 pm

@kangmingtay
Copy link
Contributor

kangmingtay commented Apr 4, 2023

Hi @johndpope, i think you'll need to complete the authorization code flow with swift natively first. You should get back an id token as mentioned in the link you provided: https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens

This id token can then be used in signInWithIdToken which checks that the id token & nonce are valid before returning the gotrue tokens. What you're doing here is essentially using apple's authentication services framework to complete the user authentication natively and then syncing the user with Supabase auth so you get back a gotrue access token JWT which you can then use for subsequent requests to Supabase (e.g. storage / postgrest / realtime / functions)

it seems no one has ever used this call signInWithIdToken in swift? presumably it's not going to work?
this function lives in js library - and we need to be hitting a golang / gotrue end point instead?
this is why where asking for native support - not through webpage with sign in via apple (which works).

Yeah just confirmed that that's unfortunately the case :/ the swift library for gotrue is community maintained and as such, it seems to have fallen behind the JS and Flutter client libs. You're welcome to make a contribution to the library to add the signInWithIdToken method which should resemble closely to the JS implementation.

Also, just looping in @maail here who's helping us out with the swift community library in case he has the bandwidth to pick this up :)

@johndpope
Copy link

johndpope commented Apr 4, 2023

ok - this looks very doable

  /// - Tag: did_complete_authorization
    func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        switch authorization.credential {
        case let appleIDCredential as ASAuthorizationAppleIDCredential:
            
            if let token = appleIDCredential.identityToken{
                print("token:",token);
               // TODO -  call the signInWithIdToken endpoint
            }
        //    if let authCode = appleIDCredential.authorizationCode{
          //      print("authorizationCode:",authCode);
            //    if let authCodeStr = String(data:authCode,encoding: .utf8){
              //      loginWithApple(authCodeStr);
             //   }
   //         }

UPDATE

working
https://github.com/johndpope/Sign-in-with-Apple-for-supabase/blob/master/ImplementingUserAuthenticationWithSignInWithApple/Juice/LoginViewController.swift

UPDATE 2 - good news - it's working ✅
Screenshot 2023-04-04 at 10 37 13 pm

Screenshot 2023-04-04 at 10 41 55 pm

@catlan - you can close this ticket. @GRSouza / @maail - it would be good to have this apple signin code in this repo to demonstrate sign in + assigning access token to auth credentials for the supabase client.

@grdsdev
Copy link
Contributor

grdsdev commented Apr 10, 2023

signInWithIdToken was added in #49 will close this issue then.

Feel free to open a new issue if needed.

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