The revoke tokens endpoint (/auth/revoke) is the only way to programmatically invalidate user tokens associated to your developer account without user interaction. This endpoint requires either a valid refresh token or access token for invalidation, as Sign in with Apple expects all apps to securely transmit and store these tokens for validation and user identity verification while managing user sessions.
If you don’t have the user’s refresh token, access token, or authorization code, you must still fulfill the user’s account deletion request and meet the account deletion requirement. You'll need to follow this workaround to manually revoke the user credentials:
Delete the user’s account data from your systems.
Direct the user to manually revoke access for your client.
Respond to the credential revoked notification to revert the client to an unauthenticated state
Important: If the manual token revocation isn’t completed, the next time the user authenticates with your client using Sign in with Apple, they won’t be presented with the initial authorization flow to enter their full name, email address, or both. This is because the user credential state managed by Sign in with Apple remains unchanged and returns the.authorizedcredential state, which may also result in the system auth UI displaying the “Continue with Apple” button.
Respond to the credential revoked notification
Once the user’s credentials are revoked by Apple, your client will receive a notification signaling the revocation event:
For apps using the Authentication Services framework to implement Sign in with Apple, register to observe the notification named credentialRevokedNotification.
For web services, if an endpoint is registered for server-to-server notifications, Apple broadcasts a notification to the specified endpoint with the consent-revokedevent type.
When receiving either notification, ensure you’ve already performed the following operations to meet the requirements of account deletion:
Deleted all user-related account data, including:
The token used for token revocation;
Any user-related data stored in your app servers; and
Any user-related data store in the Keychain or securely on disk in the native app or locally on web client.
Reverted the client to an unauthenticated state.
Securely store user tokens for account creations
For all new user account creations, follow the expected authorization flow below:
Securely transmit the identity token and authorization code to your app server.
Verify the identity token and validate the authorization code using the /auth/token endpoint.
Once the authorization code is validated, securely store the token response — including the identity token, refresh token, and access token.
Validate the refresh token up to once per day with Apple servers (to manage the lifetime of your user session and for future token revocation requests), and obtain access tokens (for future token revocation, app transfer, or user migration requests).
For information about verifying an identity token and validating tokens, visit Verifying a user and Generate and validate tokens.
If you have questions about implementing these flows, including client authorization, token validation, or token revocation, please submit a Technical Support Incident.
Sign in with Apple REST API
RSS for tagThe Sign in with Apple REST API allows your app's servers to communicate with Apple’s authentication servers.
Posts under Sign in with Apple REST API tag
48 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
Hi,
Please see TN3159: Migrating Sign in with Apple users for an app transfer for more information on the expected end-to-end app transfer and user migration flow.
Additionally, if you'd like for the iCloud and App Store engineering teams to confirm if the errors are related to a revoked authorization to previous users accounts, please submit a report via Feedback Assistant and include the following information:
Gathering required information for troubleshooting Sign in with Apple user migration
To prevent sending sensitive JSON Web Tokens (JWTs) in plain text, you should create a report in Feedback Assistant to share the details requested below. Additionally, if I determine the error is caused by an internal issue in the operating system or Apple ID servers, the appropriate engineering teams have access to the same information and can communicate with you directly for more information, if needed. Please follow the instructions below to submit your feedback.
For issues occurring with your user migration, ensure your feedback contains the following information:
the primary App ID and Services ID
the client secret for the transferring team (Team A) and the recipient team (Team B)
the failing request(s), including all parameter values, and error responses (if applicable)
the timestamp of when the issue was reproduced (optional)
screenshots or videos of errors and unexpected behaviors (optional)
Important: If providing a web service request, please ensure the client secret (JWT) has an extended expiration time (exp) of at least ten (10) business days, so I have enough time to diagnose the issue. Additionally, if your request requires access token or refresh tokens, please provide refresh tokens as they do not have a time-based expiration time; most access tokens have a maximum lifetime of one (1) hour, and will expire before I have a chance to look at the issue.
Submitting your feedback
Before you submit via Feedback Assistant, please confirm the requested information above (for your native app or web service) is included in your feedback. Failure to provide the requested information will only delay my investigation into the reported issue within your Sign in with Apple client.
After your submission to Feedback Assistant is complete, please respond in your existing Developer Forums post with the Feedback ID. Once received, I can begin my investigation and determine if this issue is caused by an error within your client, a configuration issue within your developer account, or an underlying system bug.
Cheers,
Paris X Pinkney | WWDR | DTS Engineer
Hello everyone,
We recently transferred an iOS app but didn’t generate the transfer identifier before initiating the transfer. Is it still possible to generate the transfer identifier after the transfer has been completed? If not, are there any alternative solutions or steps we can take to resolve this issue?
Thank you for any guidance!
Hello there, we have implemented the Apple sign in our site, everything is working good except from two edge cases when the JWT returned by Apple sign in does not contain the user email, these cases are:
When users choose Hide My Email when creating their account and later manually change their settings and turning off the email forwarding (Tested).
For Apple at Work & School users. i.e. younger students may not have an email address. According to Apple docs, email could be empty for Sign in with Apple at Work & School users (Not tested).
The problem is that we use the email to confirm the user authentication, but when the email is not present in the JWT, our system won't be able to find the registered user.
We're currently working on a workaround for this, but we would like to confirm that these edge cases are known by apple and also ask some questions:
Is it correct to say that: Turning off the email forwarding will cause that Apple's identity token (JWT) does not include the user's email address?
Apple at Work & School users: is there a way to identify that someone is using this type of account?
Is there any other known edge case when the email could be empty in the JWT?
Thanks in advance!
I haven't gotten any hits searching for this, so I decided to open a new thread.
The Tech Note that was mentioned in an earlier 2024 thread doesn't mention this error.
I've been trying different ways to get a token, and finally found this article that seems to be in the correct format.
https://dev.to/hasone/generate-jwt-token-for-apple-store-connect-api-using-python-3j5h
The Apple App Store Server Swift Library was supposed to have a createJWT() method, but it's gone now.
curl -v -H 'Authorization: Bearer [token]' "https://weatherkit.apple.com/api/v1/availability/37.323/122.032?country=US"
Host weatherkit.apple.com:443 was resolved.
IPv6: (none)
IPv4: 23.66.3.87, 23.66.3.70, 23.66.3.74, 23.66.3.72, 23.66.3.81, 23.66.3.75, 23.66.3.91, 23.66.3.71, 23.66.3.73
Trying 23.66.3.87:443...
Connected to weatherkit.apple.com (23.66.3.87) port 443
ALPN: curl offers h2,http/1.1
(304) (OUT), TLS handshake, Client hello (1):
CAfile: /etc/ssl/cert.pem
CApath: none
(304) (IN), TLS handshake, Server hello (2):
(304) (IN), TLS handshake, Unknown (8):
(304) (IN), TLS handshake, Certificate (11):
(304) (IN), TLS handshake, CERT verify (15):
(304) (IN), TLS handshake, Finished (20):
(304) (OUT), TLS handshake, Finished (20):
SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384 / [blank] / UNDEF
ALPN: server accepted http/1.1
Server certificate:
subject: C=US; ST=California; O=Apple Inc.; CN=weather-data.apple.com
start date: Oct 9 21:14:44 2024 GMT
expire date: Jan 7 20:21:03 2025 GMT
subjectAltName: host "weatherkit.apple.com" matched cert's "weatherkit.apple.com"
issuer: C=US; O=Apple Inc.; CN=Apple Public Server ECC CA 1 - G1
SSL certificate verify ok.
using HTTP/1.x
GET /api/v1/availability/37.323/122.032?country=US HTTP/1.1
Host: weatherkit.apple.com
User-Agent: curl/8.7.1
Accept: /
Authorization: Bearer [token]
Request completely sent off
< HTTP/1.1 401 Unauthorized
< Server: Apple
< Content-Type: application/json
< Content-Length: 26
< X-Frame-Options: SAMEORIGIN
< Strict-Transport-Security: max-age=31536000; includeSubdomains
< X-XSS-Protection: 1; mode=block
< Access-Control-Allow-Origin: *
< X-Content-Type-Options: nosniff
< Content-Security-Policy: default-src 'self';
< X-REQUEST-ID: 320cab08-acba-0127-fe19-4893dacf059c
< X-Apple-Origin: 3c6511d9-6be2-32cb-8412-efd1b1efa576
< Content-Disposition: inline;filename=f.txt
< Date: Tue, 15 Oct 2024 10:40:01 GMT
< X-Cache: TCP_MISS from a23-220-165-87.deploy.akamaitechnologies.com (AkamaiGHost/11.6.5-30d892fcde524eb1bee7eeb45111707d) (-)
< Connection: keep-alive
<
Connection #0 to host weatherkit.apple.com left intact
{"reason": "MISSING_AUTH"}
Hello,
Following a company split we are planning to transfer one of our apps, which has Sign in With Apple enabled, to another team. We want to provide a smooth migration experience for the users by minimizing downtime and avoiding the duplication of accounts in our database.
In our backend we generate a client secret using the transferring team’s ID. We then use this client secret with the “https://appleid.apple.com/auth/token” endpoint which returns the identity token.
With the above in mind, I have the following questions:
If we don’t update the team ID immediately after the transfer in our backend, will the identity token returned by the endpoint above contain the transferring team user ID in the sub field or, will it contain the recipient team user ID?
Is there any possibility that we will ever receive an identity token containing a transferring team user ID in the sub field after we accept the transfer?
Thanks,
Bruno
Hi all, I am in the process of preparing for an app transfer, and have sign-in with apple enabled. I have read the documentation thoroughly and multiple times, yet there are a few things I'd like to have a confirmation about, before taking the leap and risking that some users might experience any issues.
If I understand correctly, after the migration if a user performs a sign-in with Apple, they will send an access_token that differs from the one they were sending when the app was assigned to the old team. In case I didn't take any action that means that my system would think this was a new user given the access_token has never been seen before, and therefore it will create a new user. Is that correct?
Ok, so if that assumption is correct, I'd like to have a confirmation also of the way I intend to fix this, since we're doing an internal transfer and the database is going to be the same.
I would get a TransferID for all users in my database that have used sign-in with Apple (I have already done that for one of my test users, successfully).
After that, I will start the transfer, and accept it from the other team.
Once that is done, I will call the migrationinfo endpoint from the other team, getting all the new access_tokens related to the transfer ids.
With that information, I will update my databse, adding a relation from the new access token from team B that points to the same user as the access token that was given by team A, and I know which one it is because of the TransferID.
Does that make sense? Would it work? I'm not a fan of messing with the login logic (having a look at transfer_ids, looking for matches, and so on), especially because there doesn't seem to be a way to test this.
I believe the only risk is that a user might login after the app has been transferred but before I can upload the new access token to the database, but we can handle these (few, hopefully even zero cases) via ticketing.
These are the resources I have read so far:
https://developer.apple.com/documentation/technotes/tn3159-migrating-sign-in-with-apple-users-for-an-app-transfer#Preparing-to-migrate-users-for-an-app-transfer
https://developer.apple.com/documentation/sign_in_with_apple/transferring_your_apps_and_users_to_another_team#3546291
https://developer.apple.com/documentation/sign_in_with_apple/bringing_new_apps_and_users_into_your_team
My last question is: how can I test this before going live? Do I really have to just implement changes/update the DB and then go live, hoping that it will all work? Can't I do some sandbox transfer or anything like that? Even just creating like a "clone" of my app and transferring this one would be a huge boost for the confidence of this big leap. Thanks in advance.
I am currently working on a Visual Basic .NET project and aim to integrate an internal application with the Apple Business Manager API to access DEP (Device Enrollment Program) device data.
Specifically, I would like to request any guidance on the following aspects:
Generating a Valid Access Token: I am aware that JSON tokens are required to interact with the API, but I am unsure of the correct procedure to create a valid token for accessing the Apple Business Manager data.
How to set permissions for accessing DEP Device Data: What steps do I need to follow to obtain the necessary permissions to read DEP device data from Apple Buiness Manager? Are there specific configurations or approval processes that need to be completed within Apple Developer Account oder Apple Business Manager account (which both uses same Apple ID)?
API Endpoints and Documentation to access Business Manager by API: Could you please point me to the relevant APIs and endpoints for interacting with the DEP data? Which web requests to send where? Any documentation that outlines the API structure fur Business Manager access and how and where to obtain access tokens for it.
Thanks for any assistance as I stuck here since it is ma first project accessing Apple APIs.
Hi!
Like a bunch of people on the forums I'm having issues transferring my users from my previous Team to my new Team.
When the app was still on the old team, I successfully generated transfer_subs for every one of my apple login users.
Now, when trying to migrate them over, it ONLY works on users that have already signed in since the transfer, which is not good, I need to transfer the rest and get the new private relay emails.
Here’s a curl of how I get my access token :
I’m first generating the secret key using my team key that has apple sign in configured for it.
curl --location 'https://appleid.apple.com/auth/token'
--header 'Content-Type: application/x-www-form-urlencoded'
--data-urlencode 'grant_type=client_credentials'
--data-urlencode 'scope=user.migration'
--data-urlencode 'client_id=my.app.id'
--data-urlencode 'client_secret=***
This works and I’m getting my access token, then I try to exchange the sub token
curl --location 'https://appleid.apple.com/auth/usermigrationinfo'
--header 'Content-Type: application/x-www-form-urlencoded'
--header 'Authorization: Bearer *** '
--data-urlencode 'transfer_sub=xx.xxxx'
--data-urlencode 'client_id=my.app.id'
--data-urlencode 'client_secret=***’
This is when I receive :
{"error":"invalid_request","email_verified":false}
I’ve tried a lot of stuff, even got on the phone with an ex apple engineer and tried a bunch of stuff with him, but to no avail.
I've submitted a report on feedback assistant on the 23rd August, but no answer yet. ID: 14898085
The user migration API (https://appleid.apple.com/auth/usermigrationinfo) is inconsistent when we call it with the correct parameters and tokens to retrieve new user subs/emails for users made under a previous Entity before completing an Entity Transfer:
65% of our requests return with no new sub or email and we receive an {'error': 'invalid_request', 'email_verified': False} response back from the API when sending it our transfer subs.
34% of our requests succeed in getting a sub but no new private relay email from the same API with the same parameters- isn't it always supposed to return an email?
1% of our requests successfully responded with a new sub and private relay email.
We know it is not from anything in the request expiring because we regenerate the secrets, access_tokens, and transfer subs before making each request. All the other parameters are exactly the same as the successful API calls.
I can respond over email with more app/team-specific details or our request code. Thanks!
Recently our Sign In with Apple integration has been affected by an inconsistency in Apple token response, where the email_verified attributed has a false value in the response but inside the id_token payload the email_verifiedattribute is set to true (the correct value), our integration has been working as expected until recently and haven't found an official announcement on this change.
When making a call to https://appleid.apple.com/auth/token to exchange a code for a token using
curl -v POST "https://appleid.apple.com/auth/token" \
-H 'content-type: application/x-www-form-urlencoded' \
-d 'client_id=CLIENT_ID' \
-d 'client_secret=CLIENT_SECRET' \
-d 'code=CODE' \
-d 'grant_type=authorization_code' \
-d 'redirect_uri=REDIRECT_URI'
We're getting the following response where email_verified is set to false
{
"access_token": "XXXXXXX",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "XXXXXXX",
"id_token": "XXXXXX",
"email_verified": false
}
But by inspecting the id_token payload the email_verified attribute has the correct value: true
{
"iss": "https://appleid.apple.com",
"aud": "xxxxx",
"exp": 1724433090,
"iat": 1724346690,
"sub": "xxxxxxxxx",
"at_hash": "xxxxxxxxxxx",
"email": "my-apple-id@gmail.com",
"email_verified": true,
"auth_time": 1724346672,
"nonce_supported": true
}
I'd like to know the reason for this inconsistency, or if it is an issue and is under the Apple team's radar.
After an app transfer, I'm reached this point in this article, and I need to check if the Sign In with Apple ID users I've migrated have the "transferred" status.
This articles provides you with Swift code to check this status; does anyone know if there's a way to access this info using the Apple ID REST API, preferably with a curl command? Or do I specifically need to check this info on my app?
Here's the swift code I'm talking about:
let request = ASAuthorizationAppleIDProvider().createRequest()
// Specify the current user identifier here.
request.user = "User Identifier"
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()
We're preparing to migrate our Apple Sign-In users for an upcoming app transfer. Following this
guide, we're currently stuck on the first curl command:
curl -v POST "https://appleid.apple.com/auth/token" \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=client_credentials' \
-d 'scope=user.migration' \
-d 'client_id=CLIENT_ID' \
-d 'client_secret=CLIENT_SECRET_ISSUED_BY_TEAM_A'
Specifically, we're having issues generating the client secret, specified here.
We're using a Node.js script to generate the script; initially I realized that the private key I was signing the JWT with was wrong (I was using the App Store Connect API team key instead of a private key for use with Account & Organization Data Sharing).
Every time we try entering this curl command:
curl -v POST "https://appleid.apple.com/auth/token" \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=client_credentials' \
-d 'scope=user.migration' \
-d 'client_id=com.jumbofungames.platMaker' \
-d 'client_secret=$(node clientsecret_jwt2.js)'
Where $(node clientsecret_jwt2.js) is the command to generate the client secret; we get this error:
< HTTP/1.1 400 Bad Request
< Server: Apple
< Date: Mon, 19 Aug 2024 15:41:31 GMT
< Content-Type: application/json;charset=UTF-8
< Content-Length: 49
< Connection: keep-alive
< Pragma: no-cache
< Cache-Control: no-store
<
* Connection #1 to host appleid.apple.com left intact
{"error":"invalid_client","email_verified":false}%
Here is the script we are using to generate the Client Secret (JWT), with some variables using placeholders for privacy:
const fs = require('fs');
const jwt = require('jsonwebtoken'); // npm i jsonwebtoken
// You get privateKey, keyId, and teamId from your Apple Developer account
const privateKey = fs.readFileSync("./AuthKey_ABCDEFG123.p8") // ENTER THE PATH TO THE TEAM KEY HERE (private key file)
const keyId = "API_KEY"; // ENTER YOUR API KEY ID HERE
const teamId = "TEAM_ID"; // ENTER YOUR TEAM ID HERE
const clientId = "com.companyname.appname"; // ENTER YOUR APP ID OR SERVICES ID HERE (This is the client_id)
// Time settings (in seconds)
let now = Math.round((new Date()).getTime() / 1000); // Current time (seconds since epoch)
let nowPlus6Months = now + 15776999; // 6 months from now (maximum expiration time)
let payload = {
"iss": teamId, // The Team ID associated with your developer account
"iat": now, // Issued at time
"exp": nowPlus6Months, // Expiration time (up to 6 months)
"aud": "https://appleid.apple.com", // Audience
"sub": clientId // The App ID or Services ID
}
let signOptions = {
"algorithm": "ES256", // Algorithm for signing (ECDSA with P-256 curve and SHA-256 hash algorithm)
header : {
"alg": "ES256", // Algorithm in the header
"kid": keyId, // Key ID in the header
"typ": "JWT" // JWT type
}
};
// Generate the JWT (client_secret)
let clientSecret = jwt.sign(payload, privateKey, signOptions);
console.log(clientSecret);
module.exports = clientSecret;
If anyone has run into similar issues using this API or could shed some light on what could be going wrong, please let us know — we're at a bit of a loss here.
Hello,
I’m transferring an app from my individual account to my corporate developer account. I’m the primary owner of both accounts.
I’m trying to transfer the users that used Sign In with Apple and this is what I did:
I generated the transfer identifier for all the users that used Sign In with Apple from the database (50.000 users → 100% success rate)
I’m using the transfer identifier previously generated to create the new Apple ID and private email address of the user. (40% success rate)
I successfully generated new Apple ID and private email address for 20.000 users but for the other 30.000 users I cannot generate it because I get { error: 'invalid_request’ } on the migration endpoint (/auth/usermigrationinfo), even though I'm using the same request parameters as the ones that are working.
I couldn’t find any difference between users that could be migrated and the users that couldn’t. It doesn’t matter if they are old users or new users.
What I found is that I can generate the new Apple ID and private email address if the user signs in with Apple for the first time after the app transfer. Then I can use the “transfer_sub” that I have stored for the user to generate the new user details.
The same process worked fine for another app that I transferred. Something seems to be broken only for this app on 60% of the users that used Sign In with Apple.
Please let me know if you need further information
Best,
Cosmin
Hi everyone,
I've been working on integrating Apple Sign-In with my web app and have hit a roadblock that I can't seem to resolve.
I've successfully set up an Nginx reverse-proxy for development purposes enabling SSL/TLS to provide HTTPS.
I have configured everything using the values from the Apple Developer Console, including identifiers, keys, and IDs.
The sign-in flow works perfectly when I use my Apple ID (which is linked to my developer account). The Apple Sign-In REST API returns a JWT with my email, as expected.
However, when other users sign in with their Apple IDs, the returned JWT doesn't include their email addresses. I am aware that Apple only provides the email on the first sign-in, but this doesn't seem to be the issue here.
Below is the relevant code I'm using (Bun.js, Elysia, Arctic):
import Bun from 'bun';
import { Apple, type AppleCredentials, type AppleTokens } from 'arctic';
import type { BaseAuthAccountInfo } from './type';
import { createPrivateKey } from 'crypto';
import { sign, decode } from 'jsonwebtoken';
const {
APPLE_CLIENT_ID,
APPLE_TEAM_ID,
APPLE_KEY_ID,
APPLE_CLIENT_SECRET,
APPLE_CLIENT_SECRET_JWT,
} = Bun.env;
type AppleReponseJWTPayload = {
iss: string;
aud: string;
exp: number;
iat: number;
sub: string;
at_hash: string;
email: string;
email_verified: boolean;
auth_time: number;
nonce_supported: boolean;
};
const credentials: AppleCredentials = {
clientId: APPLE_CLIENT_ID!,
teamId: APPLE_TEAM_ID!,
keyId: APPLE_KEY_ID!,
certificate: `-----BEGIN PRIVATE KEY-----\n${APPLE_CLIENT_SECRET}\n-----END PRIVATE KEY-----`,
};
const apple = new Apple(credentials, 'https://intellioptima.com/api/v1/aus/auth/apple/callback');
const appleAuthUrl = async (state: string) => {
const appleUrl = await apple.createAuthorizationURL(state);
appleUrl.searchParams.set('response_mode', 'form_post');
appleUrl.searchParams.set('scope', 'email');
return appleUrl;
};
const getAppleTokens = async (code: string) => {
console.log('Authorization code:', code);
const appleResponse = await apple.validateAuthorizationCode(code);
console.log('Apple Response:', appleResponse);
return appleResponse;
};
const getAppleAccount = async (tokens: AppleTokens): Promise<BaseAuthAccountInfo> => {
const token = generateJWTApple();
const response = await fetch('https://appleid.apple.com/auth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
client_id: credentials.clientId,
client_secret: token,
grant_type: 'refresh_token',
refresh_token: tokens.refreshToken!,
}).toString(),
});
if (!response.ok) {
throw new Error('Failed to fetch user info');
}
const appleResponse = await response.json();
console.log('APPLE_RESPONSE', appleResponse);
const decodedUser = decode(appleResponse.id_token) as AppleReponseJWTPayload;
if (!decodedUser || !decodedUser.email) {
throw new Error('The user does not have an email address.');
}
return {
id: decodedUser.sub as string,
username: decodedUser.email.split('@')[0],
email: decodedUser.email!,
name: decodedUser.email.split('@')[0],
emailVerified: decodedUser.email_verified ?? false,
iconUrl: `https://robohash.org/${decodedUser.email.split('@')[0]}.png`,
};
};
function generateJWTApple() {
const MINUTE = 60;
const HOUR = 60 * MINUTE;
const DAY = 24 * HOUR;
const MONTH = 30 * DAY;
const tokenKey = `-----BEGIN PRIVATE KEY-----\n${APPLE_CLIENT_SECRET_JWT!.replace(/\\n/g, '\n')}\n-----END PRIVATE KEY-----`;
const privateKey = createPrivateKey(tokenKey);
const now = Math.ceil(Date.now() / 1000);
const expires = now + MONTH * 3;
const claims = {
iss: APPLE_TEAM_ID,
iat: now,
exp: expires,
aud: 'https://appleid.apple.com',
sub: 'com.intellioptima.aichat',
};
return sign(claims, privateKey, {
header: {
kid: APPLE_KEY_ID,
alg: 'ES256',
},
});
}
export { apple, appleAuthUrl, getAppleAccount, getAppleTokens };
I would greatly appreciate any insights or suggestions on what might be going wrong. I'm at a loss, and any help would be invaluable!
Thanks in advance! <3333
I'm trying to access my own financial data (I'm building a dashboard for my own personal finances) but can't seem to find an API for this.
I found FinanceKit but it sounds like you have to be a big company to use that and that and it's more of a swift SDK than a generic API endpoint.
Does anyone know how I can access my financial data without manual downloads?
Please someone help me....
I have been struggling for quite a while now configuring everything for the flow of using Apple SSI for my web app.
I have finally managed to configure a nginx reverse-proxy for development experience. Creating and working correctly with all the values from Apple Developer Console which involves the identifiers, keys and Id's.
My issue is now, that everything works for my signin flow. SO when I sign in using my AppleID which is also connected to the developer account I get signed in and Apple signin RESTAPI returns a JWT with my email.
But when everyone else signs in with their AppleID's the returned JWT doesn't have the emails. And I know that Apple only gives the email first time user signs in - but that's is not the issue.
Here is my code (using bun.js, Elysia, Arctic):
import Bun from 'bun';
import { Apple, type AppleCredentials, type AppleTokens } from 'arctic';
import type { BaseAuthAccountInfo } from './type';
import { createPrivateKey } from 'crypto';
import { sign, decode } from 'jsonwebtoken';
const {
APPLE_CLIENT_ID,
APPLE_TEAM_ID,
APPLE_KEY_ID,
APPLE_CLIENT_SECRET,
APPLE_CLIENT_SECRET_JWT,
} = Bun.env;
type AppleReponseJWTPayload = {
iss: string;
aud: string;
exp: number;
iat: number;
sub: string;
at_hash: string;
email: string;
email_verified: boolean;
auth_time: number;
nonce_supported: boolean;
};
const credentials: AppleCredentials = {
clientId: APPLE_CLIENT_ID!,
teamId: APPLE_TEAM_ID!,
keyId: APPLE_KEY_ID!,
certificate: -----BEGIN PRIVATE KEY-----\n${APPLE_CLIENT_SECRET}\n-----END PRIVATE KEY-----,
};
const apple = new Apple(credentials, 'https://intellioptima.com/api/v1/aus/auth/apple/callback');
const appleAuthUrl = async (state: string) => {
const appleUrl = await apple.createAuthorizationURL(state);
appleUrl.searchParams.set('response_mode', 'form_post');
appleUrl.searchParams.set('scope', 'email');
return appleUrl;
};
const getAppleTokens = async (code: string) => {
console.log('Authorization code:', code);
const appleResponse = await apple.validateAuthorizationCode(code);
console.log('Apple Response:', appleResponse);
return appleResponse;
};
const getAppleAccount = async (tokens: AppleTokens): Promise => {
const token = generateJWTApple();
const response = await fetch('https://appleid.apple.com/auth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
client_id: credentials.clientId,
client_secret: token,
grant_type: 'refresh_token',
refresh_token: tokens.refreshToken!,
}).toString(),
});
if (!response.ok) {
throw new Error('Failed to fetch user info');
}
const appleResponse = await response.json();
console.log('APPLE_RESPONSE', appleResponse);
const decodedUser = decode(appleResponse.id_token) as AppleReponseJWTPayload;
if (!decodedUser || !decodedUser.email) {
throw new Error('The user does not have an email address.');
}
return {
id: decodedUser.sub as string,
username: decodedUser.email.split('@')[0],
email: decodedUser.email!,
name: decodedUser.email.split('@')[0],
emailVerified: decodedUser.email_verified ?? false,
iconUrl: `https://robohash.org/${decodedUser.email.split('@')[0]}.png`,
};
};
function generateJWTApple() {
const MINUTE = 60;
const HOUR = 60 * MINUTE;
const DAY = 24 * HOUR;
const MONTH = 30 * DAY;
const tokenKey = `-----BEGIN PRIVATE KEY-----\n${APPLE_CLIENT_SECRET_JWT!.replace(/\\n/g, '\n')}\n-----END PRIVATE KEY-----`;
const privateKey = createPrivateKey(tokenKey);
const now = Math.ceil(Date.now() / 1000);
const expires = now + MONTH * 3;
const claims = {
iss: APPLE_TEAM_ID,
iat: now,
exp: expires,
aud: 'https://appleid.apple.com',
sub: 'com.intellioptima.aichat',
};
return sign(claims, privateKey, {
header: {
kid: APPLE_KEY_ID,
alg: 'ES256',
},
});
}
export { apple, appleAuthUrl, getAppleAccount, getAppleTokens };
What could be the issue???
I really hope someone out there can provide me with some details on what is going on <33333
There are two apps under our account that need to be transferred to other teams, namely A app and B app. Both apps have the function of Apple login. Now we need to transfer the original Apple login user to the new team, and we need to generate a new transfer identifier, i.e. TransferId.
We created a new p8 file for each app in the developer background, and then followed the apple document to generate the transfer identifier. A app can be generated normally, but B app cannot be generated, and the error http: 400, { error: 'invalid_grant' };
The process is as follows:
Generate clientSecret according to the key and p8 file corresponding to the sent teamID and p8 file and clientId, i.e. bundleid. ✅
Generate accessToken using clientId and clientSecret. ✅
Generate transfer identifier (auth/usermigrationinfo). This interface reports an error http: 400, { error: 'invalid_grant' }. ❌
Hello. I would like to provide both self-login/sign-up service and social login service to the app.
According to the guidelines, if an app provides a social login service, it must provide Apple Login or another login service with equivalent privacy protection features.
So, even if the app provides the company's own login/signup service, if it also provides any other social login service, do the above app review guidelines need to be considered?
Or, if we provide our own login, can we ignore the above guidelines even if we provide social login?
I don't really understand the guidelines, so I'm asking a question to get a clear answer.
Thank you for reading my long question.
Hello,
I have implemented Sign in with Apple in my iOS app and am currently trying to implement the revocation feature. However, I keep encountering an invalid_client error when calling the Apple authentication/revocation API.
Here are the details of my configuration:
Team ID: HUGD2H952H
Client ID: com.puppylink.puppylinkapp
Key ID: KXSYK98424
I am using these details to generate a client secret with the JWT ES256 algorithm. Below is the code I am using on the backend server to generate the client secret:
private fun makeClientSecret(): String {
val now: ZonedDateTime = ZonedDateTime.now(ZoneOffset.UTC)
val expirationTime: ZonedDateTime = now.plusMinutes(5) // Setting expiration time to 5 minutes
return Jwts.builder()
.setHeaderParam(JwsHeader.KEY_ID, appleProperties.keyId)
.setHeaderParam("alg", "ES256")
.setIssuer(appleProperties.teamId)
.setIssuedAt(Date.from(now.toInstant()))
.setExpiration(Date.from(expirationTime.toInstant()))
.setAudience("https://appleid.apple.com")
.setSubject(appleProperties.clientId)
.signWith(getPrivateKey(), SignatureAlgorithm.ES256)
.compact()
}
private fun getPrivateKey(): PrivateKey {
val resource = ClassPathResource(appleProperties.privateKeyFile)
val privateKey = String(Files.readAllBytes(Paths.get(resource.uri)))
val pemReader: Reader = StringReader(privateKey)
val pemParser = PEMParser(pemReader)
val converter = JcaPEMKeyConverter()
val keyInfo = pemParser.readObject() as PrivateKeyInfo
return converter.getPrivateKey(keyInfo)
}
}
Additionally, here is the code used to call the Apple authentication API from the backend server:
@Service
class AppleAuthService(
private val appleProperties: AppleProperties,
) {
private val logger = LoggerFactory.getLogger(javaClass)
private val restTemplate = RestTemplate()
fun getTokens(authorizationCode: String): TokenResponse {
try {
val clientSecret = makeClientSecret()
val formData: MultiValueMap<String, String> = LinkedMultiValueMap()
formData.add("client_id", appleProperties.clientId)
formData.add("client_secret", clientSecret)
formData.add("code", authorizationCode)
formData.add("grant_type", "authorization_code")
val headers = HttpHeaders()
headers.contentType = MediaType.APPLICATION_FORM_URLENCODED
val requestEntity = HttpEntity(formData, headers)
val response =
restTemplate.postForObject(
"https://appleid.apple.com/auth/token",
requestEntity,
TokenResponse::class.java,
)
return response ?: throw RuntimeException("Failed to retrieve tokens from Apple")
} catch (ex: Exception) {
logger.error("Error retrieving tokens: ", ex)
throw ex
}
}
data class TokenResponse(
val access_token: String,
val expires_in: Long,
val id_token: String,
val refresh_token: String,
val token_type: String,
)
Despite generating the client secret correctly, I am still receiving the invalid_client error when calling the API. Could you please help me identify the cause of this error and provide guidance on how to resolve it?
Thank you.
Hi Team,
We are planning to automate ABM export. We dont want to download export which contains device inventory for example, S/N, IMEI, Reseller ID, etc.
Is there any way to automate it or has Apple made their APIs available?
Any help would be appreciated.
Regards!