forked from fastify/fastify-oauth2
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapple.js
130 lines (113 loc) · 4.34 KB
/
apple.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
'use strict'
/**
* This example assumes the use of the npm package `apple-signin-auth` in your code.
* This library is not included with fastify-oauth2. If you wish to implement
* the verification part of Apple's Sign In REST API yourself,
* look at {@link https://github.com/a-tokyo/apple-signin-auth} to see how they did
* it, or look at {@link https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api}
* for more details on how to do it from scratch.
*/
const fastify = require('fastify')({ logger: { level: 'trace' } })
const appleSignin = require('apple-signin-auth')
// const oauthPlugin = require('fastify-oauth2')
const oauthPlugin = require('..')
// All fields below must come from environment variables
const [CLIENT_ID, TEAM_ID, PRIVATE_KEY, KEY_ID] = ['<CLIENT_ID>', '<TEAM_ID>', '<PRIVATE_KEY>', '<KEY_ID>']
// In Apple OAuth2 the CLIENT_SECRET is not static and must be generated
const CLIENT_SECRET = generateClientSecret()
fastify.register(oauthPlugin, {
name: 'appleOAuth2',
credentials: {
client: {
id: CLIENT_ID,
secret: CLIENT_SECRET
},
auth: oauthPlugin.APPLE_CONFIGURATION,
options: {
/**
* Based on offical Apple OAuth2 docs, an HTTP POST request is sent to the redirectURI for the `form_post` value.
* And the result of the authorization is stored in the body as application/x-www-form-urlencoded content type.
* See {@link https://developer.apple.com/documentation/sign_in_with_apple/request_an_authorization_to_the_sign_in_with_apple_server}
*/
authorizationMethod: 'body'
}
},
startRedirectPath: '/login/apple',
callbackUri: 'http://localhost:3000/login/apple/callback'
})
fastify.get('/login/apple/callback', function (request, reply) {
/**
* NOTE: Apple returns the "user" object only the 1st time the user authorizes the app.
* For more information, visit https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api/authenticating_users_with_sign_in_with_apple
*/
const { code, state, error, user } = request.body
if (user) {
// Make sure to validate and persist it. All subsequent authorization requests will not contain the user object
}
if (!state) {
// If the endpoint was not redirected from social oauth flow
throw new Error('Illegal invoking of endpoint.')
}
if (error === Error.CancelledAuth) {
// If a user cancelled authorization process, redirect him back to the app
const webClientUrl = '<WEB_CLIENT_URL>'
reply.status(303).redirect(webClientUrl)
}
const authCodeFlow = { ...request, query: { code, state } }
this.appleOAuth2
.getAccessTokenFromAuthorizationCodeFlow(authCodeFlow, (err, result) => {
if (err) {
reply.send(err)
return
}
decryptToken(result.id_token)
.then(payload => {
const userAppleId = payload.sub
reply.send(userAppleId)
})
.catch(err => {
// Token is not verified
reply.send(err)
})
})
})
/**
* Decrypts Token from Apple and returns decrypted user's info
*
* @param { string } token Info received from Apple's Authorization flow on Token request
* @returns { object } Decrypted user's info
*/
function decryptToken (token) {
/**
* NOTE: Data format returned by Apple
*
* {
* email: 'user_email@abc.com',
* iss: 'https://appleid.apple.com'
* sub: '10*****************27' // User ID,
* email_verified: 'true',
* is_private_email: 'false',
* ...
* }
*
* PS: All fields can be found here - {@link https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api/authenticating_users_with_sign_in_with_apple}
*/
return appleSignin.verifyIdToken(token, CLIENT_ID)
}
/**
* Generates Apple's OAuth2 secret key based on expiration date, Client ID, Team ID, Private key and Key ID.
* See more {@link https://github.com/a-tokyo/apple-signin-auth} for implementation details.
*
* @returns { string } Apple Secret Key
*/
function generateClientSecret () {
const expiresIn = 180 // in days (6 months) - custom time set based on requirements
return appleSignin.getClientSecret({
clientID: CLIENT_ID,
teamID: TEAM_ID,
privateKey: PRIVATE_KEY,
keyIdentifier: KEY_ID,
expAfter: expiresIn * 24 * 3600 // in seconds
})
}
fastify.listen({ port: 3000 })