Search code examples
google-cloud-functionsapple-developerapple-sign-insign-in-with-apple

Sign in with Apple Android implementation


I'm trying to implement this using a Firebase Function. The authorization part works well and my endpoint get's called. Still when I try to hit the token endpoint I always get {"error":"invalid_client"}. Below you have my firebase function with a test key:

import * as functions from 'firebase-functions';
import * as rp from "request-promise-native";
import * as buildURL from "build-url";
import * as jwt from "jsonwebtoken";

const privateKey = '-----BEGIN PRIVATE KEY-----\n' +
    'MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgP8TqTrmSMJeaBZv+\n' +
    'eOHFL8Y4b1jTIrb8d7FVIFR7vVOgCgYIKoZIzj0DAQehRANCAAQSqhtbJHJ0J24T\n' +
    'vAxnANEY7m1yZJ/J7VTGXHGXWic/8xgvOIkhGnmdwgD/oCmIvjo/6yL1hlEx51hr\n' +
    'EGErDIg1\n' +
    '-----END PRIVATE KEY-----';

const home = 'eu.long1.signinwithappleexample://eu.long1.signinwithappleexample/auth';
const clientId = 'eu.long1.signinwithappleexample.android';
const keyId = 'TB943KQS2Y';
const teamId = 'DKY2FBVP6L';
const audience = 'https://appleid.apple.com';
const tokenEndpoint = 'https://appleid.apple.com/auth/token';

export const callback = functions.https.onRequest(async (request, response) => {
    const data = <AuthResponse>request.body;
    let params: { [key: string]: string } = {state: data.state};
    if (data.error) {
        response.redirect(buildURL(home, {queryParams: {...params, error: data.error}}));
    } else if (!data.code) {
        response.redirect(buildURL(home, {queryParams: {...params, error: 'no_code'}}));
    } else {
        try {
            const jwtOptions = {
                keyid: keyId,
                algorithm: 'ES256',
                issuer: teamId,
                audience: audience,
                subject: clientId,
                expiresIn: 60
            };
            const clientSecret = jwt.sign({}, privateKey, jwtOptions);
            const result = await rp.post(tokenEndpoint, {
                headers: {'Content-Type': 'application/x-www-form-urlencoded'},
                formData: {
                    client_id: clientId,
                    client_secret: clientSecret,
                    code: data.code,
                    grant_type: 'authorization_code',
                    redirect_uri: 'https://us-central1-flutter-sdk.cloudfunctions.net/callback'
                }
            });
            response.redirect(buildURL(home, {queryParams: {...params, result: result.body}}));
        } catch (e) {
            response.redirect(buildURL(home, {queryParams: {...params, error: e.error}}));
        }
    }
});

interface AuthResponse {
    readonly code: string | undefined
    readonly id_token: string | undefined
    readonly state: string
    readonly user: AuthResponseUser | undefined
    readonly error: string | undefined
}

interface AuthResponseUser {
    readonly name: AuthResponseUserName | undefined
    readonly email: string | undefined
}

interface AuthResponseUserName {
    readonly firstName: string | undefined
    readonly lastName: string | undefined
}

My guess is that somehow I don't sign the client_secret properly since every time I tried to validate it on jwt.io, it said it has an invalid signature.


Solution

  • The best thing you can do if you have a Apple developer account, witch it seems you have, is to contact the support team. Only them can tell you for sure what the issue is. Your code looks good at first glance so more logs should offer you more insight.