diff --git a/apps/docs/components/AppleSecretGenerator.tsx b/apps/docs/components/AppleSecretGenerator.tsx new file mode 100644 index 00000000000..7bfeed67dcc --- /dev/null +++ b/apps/docs/components/AppleSecretGenerator.tsx @@ -0,0 +1,193 @@ +import { useState } from 'react' +import { Input, Button } from 'ui' +import Admonition from '~/components/Admonition' + +function base64URL(value: string) { + return globalThis.btoa(value).replace(/[=]/g, '').replace(/[+]/g, '-').replace(/[\/]/g, '_') +} + +/* +Convert a string into an ArrayBuffer +from https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String +*/ +function stringToArrayBuffer(value: string) { + const buf = new ArrayBuffer(value.length) + const bufView = new Uint8Array(buf) + for (let i = 0; i < value.length; i++) { + bufView[i] = value.charCodeAt(i) + } + return buf +} + +function arrayBufferToString(buf) { + return String.fromCharCode.apply(null, new Uint8Array(buf)) +} + +const generateAppleSecretKey = async ( + kid: string, + iss: string, + sub: string, + file: File +): Promise<{ kid: string; jwt: string; exp: number }> => { + if (!kid) { + const match = file.name.match(/AuthKey_([^.]+)[.].*$/i) + if (match && match[1]) { + kid = match[1] + } + } + + if (!kid) { + throw new Error( + `No Key ID provided. The file "${file.name}" does not follow the AuthKey_XXXXXXXXXX.p8 pattern. Please provide a Key ID manually.` + ) + } + + const contents = await file.text() + + if (!contents.match(/^\s*-+BEGIN PRIVATE KEY-+[^-]+-+END PRIVATE KEY-+\s*$/i)) { + throw new Error(`Chosen file does not appear to be a PEM encoded PKCS8 private key file.`) + } + + // remove PEM headers and spaces + const pkcs8 = stringToArrayBuffer( + globalThis.atob(contents.replace(/-+[^-]+-+/g, '').replace(/\s+/g, '')) + ) + + const privateKey = await globalThis.crypto.subtle.importKey( + 'pkcs8', + pkcs8, + { + name: 'ECDSA', + namedCurve: 'P-256', + }, + true, + ['sign'] + ) + + const iat = Math.floor(Date.now() / 1000) + const exp = iat + 180 * 24 * 60 * 60 + + const jwt = [ + base64URL(JSON.stringify({ typ: 'JWT', kid, alg: 'ES256' })), + base64URL( + JSON.stringify({ + iss, + sub, + iat, + exp, + aud: 'https://appleid.apple.com', + }) + ), + ] + + const signature = await globalThis.crypto.subtle.sign( + { + name: 'ECDSA', + hash: 'SHA-256', + }, + privateKey, + stringToArrayBuffer(jwt.join('.')) + ) + + jwt.push(base64URL(arrayBufferToString(signature))) + + return { kid, jwt: jwt.join('.'), exp } +} + +const AppleSecretGenerator = () => { + const [file, setFile] = useState({ file: null as File | null }) + const [teamID, setTeamID] = useState('') + const [serviceID, setServiceID] = useState('') + const [keyID, setKeyID] = useState('') + const [secretKey, setSecretKey] = useState('') + const [expiresAt, setExpiresAt] = useState('') + const [error, setError] = useState('') + + return ( + <> + setTeamID(e.target.value.trim())} + /> + setServiceID(e.target.value.trim())} + /> + setKeyID(e.target.value.trim())} + /> +
+ { + setFile({ file: e.target.files[0] }) + }} + /> +
+
+ + + + {error && {error}} + + {secretKey && ( + <> +
+ + + )} + + ) +} + +export default AppleSecretGenerator diff --git a/apps/docs/pages/guides/auth/social-login/auth-apple.mdx b/apps/docs/pages/guides/auth/social-login/auth-apple.mdx index a41cc292f61..8f72a47a5d2 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-apple.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-apple.mdx @@ -1,4 +1,5 @@ import Layout from '~/layouts/DefaultGuideLayout' +import AppleSecretGenerator from '~/components/AppleSecretGenerator' export const meta = { id: 'auth-apple', @@ -89,69 +90,15 @@ Now you'll need to download a `secret key` file from Apple that will be used to - Save the downloaded file -- this contains your "secret key" that will be used to generate your `client_secret`. - Click `Done` at the top right. -## Generate a `client_secret` +## Generate a client secret -The `secret key` you downloaded is used to create the `client_secret` string you'll need to authenticate your users. +You need to configure a client secret when using Sign in with Apple for Web. This is a specially crafted [JWT signed with a secret key downloaded from Apple's Developer Center](https://developer.apple.com/documentation/signinwithapplerestapi/generate_and_validate_tokens). -According to the [Apple Docs](https://developer.apple.com/documentation/signinwithapplerestapi/generate_and_validate_tokens) it needs to be a JWT -token encrypted using the Elliptic Curve Digital Signature Algorithm (ECDSA) with the P-256 curve and the SHA-256 hash algorithm. + + Use this tool to generate a new Apple client secret. No keys leave your browser! + -At this time, the easiest way to generate this JWT token is with [Ruby](https://www.ruby-lang.org/en/). -If you don't have Ruby installed, you can [Download Ruby Here](https://www.ruby-lang.org/en/downloads). - -- Install Ruby (or check to make sure it's installed on your system). -- Install [ruby-jwt](https://github.com/jwt/ruby-jwt). -- From the command line, run: `sudo gem install jwt`. - -Create the script below using a text editor: `secret_gen.rb` - -```ruby -require "jwt" - -key_file = "Path to the private key" -team_id = "Your Team ID" -client_id = "The Service ID of the service you created" -key_id = "The Key ID of the private key" - -validity_period = 180 # In days. Max 180 (6 months) according to Apple docs. - -private_key = OpenSSL::PKey::EC.new IO.read key_file - -token = JWT.encode( - { - iss: team_id, - iat: Time.now.to_i, - exp: Time.now.to_i + 86400 * validity_period, - aud: "https://appleid.apple.com", - sub: client_id - }, - private_key, - "ES256", - header_fields= - { - kid: key_id - } -) -puts token -``` - -1. Edit the `secret_gen.rb` file: - -- `key_file` = "Path to the private key you downloaded from Apple". It should look like this: `AuthKey_XXXXXXXXXX.p8`. -- `team_id` = "Your Team ID". This is found at the Apple Developer website, under Membership details. This is a 10-character alphanumeric string called "Team ID". Alternatively, this can be seen next to your name in the upper right when viewing your Certificates, Identifiers & Profiles. -- `client_id` = "The Service ID of the service you created". This is the `Services ID` you created in the above step `Obtain a Services ID`. If you've lost this ID, you can find it in the Apple Developer Site: - - Go to `Certificates, Identifiers & Profiles`. - - Click `Identifiers` at the left. - - At the top right drop-down, select `Services IDs`. - - Find your Identifier in the list (i.e. app.com.acme.roadrunner). -- `key_id` = "The Key ID of the private key". This can be found in the name of your downloaded secret file (For a file named `AuthKey_XXXXXXXXXX.p8` your key_id is `XXXXXXXXXX`). If you've lost this ID, you can find it in the Apple Developer Site: - - Go to `Certificates, Identifiers & Profiles`. - - Click `Keys` at the left. - - Click on your newly-created key in the list. - - Look under `Key ID` to find your key_id. - -2. From the command line, run: `ruby secret_gen.rb > client_secret.txt`. -3. Your `client_secret` is now stored in this `client_secret.txt` file. + ## Add your OAuth credentials to Supabase @@ -180,8 +127,6 @@ async function signout() { ## Resources - [Apple Developer Account](https://developer.apple.com). -- [Ruby](https://www.ruby-lang.org/en/) Docs. -- [ruby-jwt](https://github.com/jwt/ruby-jwt) library. - Thanks to [Janak Amarasena](https://medium.com/@janakda) who did all the heavy lifting in [How to configure Sign In with Apple](https://medium.com/identity-beyond-borders/how-to-configure-sign-in-with-apple-77c61e336003). export const Page = ({ children }) =>