signinwith 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/index.js +24 -15
  2. package/package.json +2 -4
  3. package/react.jsx +196 -37
  4. package/readme.md +168 -106
package/index.js CHANGED
@@ -1,13 +1,7 @@
1
- import { OAuth2Client } from 'google-auth-library';
2
- import appleSigninAuth from 'apple-signin-auth';
3
-
4
1
  export const verifySigninGoogle = async (config, verificationData) => {
5
- const client = new OAuth2Client(config.clientId);
6
- const ticket = await client.verifyIdToken({
7
- idToken: verificationData.credential,
8
- audience: config.clientId,
9
- });
10
- const payload = ticket.getPayload();
2
+ const res = await fetch(`https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=${verificationData.credential}`);
3
+ const payload = await res.json();
4
+ if (payload.aud !== config.clientId) return { success: false, error: 'Invalid aud' };
11
5
  return payload.email ? { success: true, email: payload.email } : { success: false, error: 'Email not found' };
12
6
  };
13
7
 
@@ -19,18 +13,33 @@ export const verifySigninMeta = async (config, verificationData) => {
19
13
 
20
14
  export const verifySigninApple = async (config, verificationData) => {
21
15
  const { id_token } = verificationData;
22
- const result = await appleSigninAuth.verifyIdToken(id_token, {
23
- audience: config.clientId,
24
- ignoreExpiration: true,
16
+ const form = new FormData();
17
+ form.append('id_token', id_token);
18
+ form.append('client_id', config.clientId);
19
+ const res = await fetch('https://appleid.apple.com/auth/verify', {
20
+ method: 'POST',
21
+ body: form,
25
22
  });
23
+ const result = await res.json();
24
+ if (!result.success) return { success: false, error: result.error || 'Invalid Apple signin' };
26
25
  return result.email ? { success: true, email: result.email } : { success: false, error: 'Email not available from Apple' };
27
26
  };
27
+ export const verifySigninDiscord = async (config, verificationData) => {
28
+ const res = await fetch('https://discord.com/api/v10/users/@me', {
29
+ headers: {
30
+ authorization: `Bearer ${verificationData.accessToken}`,
31
+ },
32
+ });
33
+ const profile = await res.json();
34
+ return profile.email ? { success: true, email: profile.email } : { success: false, error: 'Email not available from Discord' };
35
+ };
28
36
 
29
37
  export default verifySignin = async (services, service, verificationData) => {
30
38
  try {
31
- if (service === 'google') return await verifySigninGoogle(services.google, verificationData);
32
- if (service === 'meta') return await verifySigninMeta(services.meta, verificationData);
33
- if (service === 'apple') return await verifySigninApple(services.apple, verificationData);
39
+ if (services.google && service === 'google') return await verifySigninGoogle(services.google, verificationData);
40
+ if (services.meta && service === 'meta') return await verifySigninMeta(services.meta, verificationData);
41
+ if (services.apple && service === 'apple') return await verifySigninApple(services.apple, verificationData);
42
+ if (services.discord && service === 'discord') return await verifySigninDiscord(services.discord, verificationData);
34
43
  return { success: false, error: 'Unsupported service' };
35
44
  } catch (err) {
36
45
  return { success: false, error: err.message || 'Unknown error' };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "signinwith",
3
- "version": "1.0.2",
4
- "description": "Simple and straightforward library for sign in / sign up with thirdparty oAuth services like Google, Meta, Apple...",
3
+ "version": "1.0.3",
4
+ "description": "Simple and straightforward library for sign in / sign up with thirdparty oAuth services like Google, Meta, Apple, Discord...",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  },
@@ -28,8 +28,6 @@
28
28
  "author": "ybouane",
29
29
  "license": "ISC",
30
30
  "dependencies": {
31
- "apple-signin-auth": "^2.0.0",
32
- "google-auth-library": "^9.15.1",
33
31
  "react": "^19.1.0"
34
32
  }
35
33
  }
package/react.jsx CHANGED
@@ -12,7 +12,12 @@ const AppleIcon = () => (
12
12
  <svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="814" height="1000"><path fill="#FFFFFF" d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76.5 0-103.7 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z"/></svg>
13
13
  );
14
14
 
15
- // Subcomponent: Facebook (Facebook)
15
+ // Subcomponent: Discord Icon
16
+ const DiscordIcon = () => (
17
+ <svg viewBox="0 -28.5 256 256" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M216.856 16.597A208.502 208.502 0 0 0 164.042 0c-2.275 4.113-4.933 9.645-6.766 14.046-19.692-2.961-39.203-2.961-58.533 0-1.832-4.4-4.55-9.933-6.846-14.046a207.809 207.809 0 0 0-52.855 16.638C5.618 67.147-3.443 116.4 1.087 164.956c22.169 16.555 43.653 26.612 64.775 33.193A161.094 161.094 0 0 0 79.735 175.3a136.413 136.413 0 0 1-21.846-10.632 108.636 108.636 0 0 0 5.356-4.237c42.122 19.702 87.89 19.702 129.51 0a131.66 131.66 0 0 0 5.355 4.237 136.07 136.07 0 0 1-21.886 10.653c4.006 8.02 8.638 15.67 13.873 22.848 21.142-6.58 42.646-16.637 64.815-33.213 5.316-56.288-9.08-105.09-38.056-148.36ZM85.474 135.095c-12.645 0-23.015-11.805-23.015-26.18s10.149-26.2 23.015-26.2c12.867 0 23.236 11.804 23.015 26.2.02 14.375-10.148 26.18-23.015 26.18Zm85.051 0c-12.645 0-23.014-11.805-23.014-26.18s10.148-26.2 23.014-26.2c12.867 0 23.236 11.804 23.015 26.2 0 14.375-10.148 26.18-23.015 26.18Z" fill="#FFFFFF"/></svg>
18
+ );
19
+
20
+ // Subcomponent: Facebook
16
21
  export function SignInWithFacebook({ service, onSignin, onError }) {
17
22
  useEffect(() => {
18
23
  if (!window.FB) {
@@ -41,20 +46,46 @@ export function SignInWithFacebook({ service, onSignin, onError }) {
41
46
  window.FB.login(function (response) {
42
47
  if (response.authResponse) {
43
48
  onSignin('facebook', { accessToken: response.authResponse.accessToken });
49
+ } else {
50
+ onError?.('Failed to log in with Facebook.');
44
51
  }
45
52
  }, { scope: 'email,public_profile' });
46
53
  };
47
54
 
48
55
  return <button className="signinwith-button signinwith-button-facebook" onClick={handleLogin}><FacebookIcon />Continue with Facebook</button>;
49
56
  }
57
+
50
58
  // Subcomponent: Google
51
59
  export function SignInWithGoogle({ service, onSignin, onError }) {
52
60
  useEffect(() => {
53
- const script = document.createElement('script');
54
- script.src = 'https://accounts.google.com/gsi/client';
55
- script.async = true;
56
- script.defer = true;
57
- script.onload = () => {
61
+ const scriptId = 'google-gsi-script';
62
+ let script = document.getElementById(scriptId);
63
+
64
+ if (!script) {
65
+ script = document.createElement('script');
66
+ script.id = scriptId;
67
+ script.src = 'https://accounts.google.com/gsi/client';
68
+ script.async = true;
69
+ script.defer = true;
70
+ script.onload = () => {
71
+ if (window.google?.accounts?.id) {
72
+ window.google.accounts.id.initialize({
73
+ client_id: service.clientId,
74
+ use_fedcm_for_prompt: true,
75
+ callback: (res) => {
76
+ onSignin('google', { credential: res.credential });
77
+ }
78
+ });
79
+ } else {
80
+ onError?.('Google Sign-In script loaded but initialization failed.');
81
+ }
82
+ };
83
+ script.onerror = () => {
84
+ onError?.('Failed to load Google Sign-In script.');
85
+ };
86
+ document.body.appendChild(script);
87
+ } else if (window.google?.accounts?.id) {
88
+ // If script already exists, ensure it's initialized (might be needed if component remounts)
58
89
  window.google.accounts.id.initialize({
59
90
  client_id: service.clientId,
60
91
  use_fedcm_for_prompt: true,
@@ -62,23 +93,33 @@ export function SignInWithGoogle({ service, onSignin, onError }) {
62
93
  onSignin('google', { credential: res.credential });
63
94
  }
64
95
  });
65
- };
66
- document.body.appendChild(script);
96
+ }
67
97
 
98
+
99
+ // Cleanup function does not remove the script to allow multiple instances
100
+ // or remounts without reloading. Google's script handles this internally.
68
101
  return () => {
69
- const scriptTag = document.querySelector('script[src="https://accounts.google.com/gsi/client"]');
70
- if (scriptTag) {
71
- document.body.removeChild(scriptTag);
72
- }
102
+ // Optional: You might want to hide the prompt if the component unmounts
103
+ // window.google?.accounts?.id?.cancel();
73
104
  };
74
- }, [service.clientId]);
105
+ }, [service.clientId, onSignin, onError]);
75
106
 
76
107
  const handleGoogleLogin = (e) => {
77
108
  e.stopPropagation();
78
109
  e.preventDefault();
79
- window.google.accounts.id.prompt((notification) => {
80
- onError?.(notification.getDismissedReason() || 'Sign-in with Google failed.');
81
- });
110
+ if (window.google?.accounts?.id) {
111
+ window.google.accounts.id.prompt((notification) => {
112
+ // Handle prompt UI notifications (e.g., closed, error)
113
+ if (notification.isNotDisplayed() || notification.isSkippedMoment()) {
114
+ // Potentially trigger a backup UX or just log
115
+ onError?.(notification.getNotDisplayedReason() || notification.getSkippedReason() || 'Google Sign-In prompt was not displayed or was skipped.');
116
+ } else if (notification.isDismissedMoment()) {
117
+ onError?.(notification.getDismissedReason() || 'Google Sign-In prompt was dismissed.');
118
+ }
119
+ });
120
+ } else {
121
+ onError?.('Google Sign-In is not initialized.');
122
+ }
82
123
  };
83
124
 
84
125
  return (
@@ -91,38 +132,156 @@ export function SignInWithGoogle({ service, onSignin, onError }) {
91
132
  // Subcomponent: Apple
92
133
  export function SignInWithApple({ service, onSignin, onError }) {
93
134
  useEffect(() => {
94
- const script = document.createElement('script');
95
- script.src = 'https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js';
96
- script.onload = () => {
97
- window.AppleID.auth.init({
98
- clientId: service.clientId,
99
- scope: 'email name',
100
- redirectURI: window.location.origin + '/auth/apple/callback',
101
- usePopup: true,
102
- });
103
- window.addEventListener('AppleIDSignInOnSuccess', (e) => {
104
- onSignin('apple', e.detail.authorization);
105
- });
135
+ const scriptId = 'apple-auth-script';
136
+ let script = document.getElementById(scriptId);
137
+
138
+ const handleAppleSignInSuccess = (event) => {
139
+ onSignin('apple', event.detail.authorization);
140
+ };
141
+ const handleAppleSignInFailure = (event) => {
142
+ onError?.(event.detail.error || 'Sign in with Apple failed.');
143
+ };
144
+
145
+ if (!script) {
146
+ script = document.createElement('script');
147
+ script.id = scriptId;
148
+ script.src = 'https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js';
149
+ script.async = true;
150
+ script.defer = true;
151
+ script.onload = () => {
152
+ try {
153
+ window.AppleID.auth.init({
154
+ clientId: service.clientId,
155
+ scope: service.scope || 'email name', // Default scope
156
+ redirectURI: service.redirectUri, // Default redirect URI
157
+ usePopup: true,
158
+ });
159
+ } catch (error) {
160
+ console.error("Apple Sign In initialization failed:", error);
161
+ onError?.('Failed to initialize Apple Sign In.');
162
+ return; // Stop if init fails
163
+ }
164
+ // Add event listeners after successful initialization
165
+ document.addEventListener('AppleIDSignInOnSuccess', handleAppleSignInSuccess);
166
+ document.addEventListener('AppleIDSignInOnFailure', handleAppleSignInFailure);
167
+ };
168
+ script.onerror = () => {
169
+ onError?.('Failed to load Apple Sign In script.');
170
+ };
171
+ document.body.appendChild(script);
172
+ } else {
173
+ // If script exists, re-add listeners in case component remounted
174
+ document.removeEventListener('AppleIDSignInOnSuccess', handleAppleSignInSuccess);
175
+ document.removeEventListener('AppleIDSignInOnFailure', handleAppleSignInFailure);
176
+ document.addEventListener('AppleIDSignInOnSuccess', handleAppleSignInSuccess);
177
+ document.addEventListener('AppleIDSignInOnFailure', handleAppleSignInFailure);
178
+ }
179
+
180
+ return () => {
181
+ // Cleanup listeners when component unmounts
182
+ document.removeEventListener('AppleIDSignInOnSuccess', handleAppleSignInSuccess);
183
+ document.removeEventListener('AppleIDSignInOnFailure', handleAppleSignInFailure);
106
184
  };
107
- document.body.appendChild(script);
108
- }, [service.clientId]);
109
- const handleAppleLogin = (e) => {
185
+ }, [service.clientId, service.scope, service.redirectUri, service.usePopup, onSignin, onError]);
186
+
187
+ const handleAppleLogin = async (e) => {
110
188
  e.stopPropagation();
111
189
  e.preventDefault();
112
- window.AppleID.auth.signIn();
190
+ try {
191
+ if (window.AppleID?.auth) {
192
+ await window.AppleID.auth.signIn();
193
+ } else {
194
+ onError?.('Apple Sign In is not initialized.');
195
+ }
196
+ } catch (error) {
197
+ // This catch might handle errors from the signIn() call itself,
198
+ // though the event listener is the primary mechanism.
199
+ console.error("Apple Sign In error:", error);
200
+ onError?.('An error occurred during Apple Sign In.');
201
+ }
113
202
  };
114
203
  return <button className="signinwith-button signinwith-button-apple" onClick={handleAppleLogin}><AppleIcon />Continue with Apple</button>;
115
204
  }
116
205
 
206
+ // Subcomponent: Discord
207
+ export function SignInWithDiscord({ service, onSignin, onError }) {
208
+ const handleDiscordLogin = (e) => {
209
+ e.stopPropagation();
210
+ e.preventDefault();
211
+
212
+ const { clientId, redirectUri, scope = 'identify email' } = service; // Default scopes
213
+
214
+ if (!clientId || !redirectUri) {
215
+ console.error("Discord service configuration missing clientId or redirectUri.");
216
+ onError?.("Discord configuration is incomplete.");
217
+ return;
218
+ }
219
+
220
+ try {
221
+ const params = new URLSearchParams({
222
+ client_id: clientId,
223
+ redirect_uri: redirectUri,
224
+ response_type: 'code',
225
+ scope: scope,
226
+ });
227
+
228
+ const discordAuthUrl = `https://discord.com/api/oauth2/authorize?${params.toString()}`;
229
+
230
+ window.open(discordAuthUrl);
231
+ } catch (error) {
232
+ console.error("Failed to initiate Discord login:", error);
233
+ onError?.("Failed to initiate Discord login.");
234
+ }
235
+ };
236
+ useEffect(() => {
237
+ const handleMessage = (event) => {
238
+ if (event.origin !== window.location.origin) {
239
+ return; // Only accept messages from the same origin
240
+ }
241
+ if (event.data && event.data.type === 'discordAuth') {
242
+ if (event.data.code) {
243
+ onSignin('discord', { code: event.data.code });
244
+ } else if (event.data.error) {
245
+ onError?.(`Discord login error: ${event.data.error}`);
246
+ } else {
247
+ onError?.('Unknown Discord login error.');
248
+ }
249
+ }
250
+ };
251
+ window.addEventListener('message', handleMessage);
252
+ return () => {
253
+ window.removeEventListener('message', handleMessage);
254
+ };
255
+ }, [onSignin, onError]);
256
+
257
+ return (
258
+ <button className="signinwith-button signinwith-button-discord" onClick={handleDiscordLogin}>
259
+ <DiscordIcon />Continue with Discord
260
+ </button>
261
+ );
262
+ }
263
+
264
+
117
265
  // Main SignInWith Component
118
266
  export default function SignInWith({ onSignin, onError, services, theme = 'light' }) {
119
267
  return (
120
268
  <div className={`signinwith-container signinwith-theme-${theme}`}>
121
- {Object.entries(services).map(([key, config]) => {
122
- if (key === 'google') return <SignInWithGoogle key={key} service={config} onSignin={onSignin} onError={onError} />;
123
- if (key === 'facebook') return <SignInWithFacebook key={key} service={config} onSignin={onSignin} onError={onError} />;
124
- if (key === 'apple') return <SignInWithApple key={key} service={config} onSignin={onSignin} onError={onError} />;
125
- return null;
269
+ {Object.entries(services || {}).map(([key, config]) => { // Added default {} for services
270
+ if (!config) return null; // Skip if config is null/undefined
271
+
272
+ switch (key.toLowerCase()) { // Use lowercase key for case-insensitivity
273
+ case 'google':
274
+ return <SignInWithGoogle key={key} service={config} onSignin={onSignin} onError={onError} />;
275
+ case 'facebook':
276
+ return <SignInWithFacebook key={key} service={config} onSignin={onSignin} onError={onError} />;
277
+ case 'apple':
278
+ return <SignInWithApple key={key} service={config} onSignin={onSignin} onError={onError} />;
279
+ case 'discord':
280
+ return <SignInWithDiscord key={key} service={config} onSignin={onSignin} onError={onError} />;
281
+ default:
282
+ console.warn(`Unsupported service key: ${key}`);
283
+ return null;
284
+ }
126
285
  })}
127
286
  </div>
128
287
  );
package/readme.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Sign In With
2
2
 
3
- A simple and straightforward library for adding "Sign in with..." buttons (Google, Facebook/Meta, Apple) to your web application, handling both the frontend UI (React) and backend verification.
3
+ A simple and straightforward library for adding "Sign in with..." buttons (Google, Facebook/Meta, Apple, Discord) to your web application, handling both the frontend UI (React) and backend verification.
4
4
 
5
5
  ## Features
6
6
 
7
- * Easy integration for Google, Facebook (Meta), and Apple sign-in.
7
+ * Easy integration for Google, Facebook (Meta), Apple, and Discord sign-in.
8
8
  * React components for the frontend buttons.
9
9
  * Backend utility functions to verify the identity tokens/access tokens.
10
10
  * Basic customizable styling.
@@ -27,47 +27,51 @@ import SignInWith from 'signinwith/react';
27
27
  import 'signinwith/styles.css'; // Import the styles
28
28
 
29
29
  function App() {
30
- // Configuration for the services you want to enable
31
- const services = {
32
- google: {
33
- clientId: 'YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com',
34
- },
35
- meta: {
36
- appId: 'YOUR_FACEBOOK_APP_ID',
37
- },
38
- apple: {
39
- clientId: 'YOUR_APPLE_SERVICE_ID', // e.g., com.mywebsite.signin
40
- // redirectURI is automatically set to window.location.origin + '/auth/apple/callback'
41
- // Ensure this callback route exists or handle the popup flow appropriately.
42
- },
43
- };
44
-
45
- // Callback function when sign-in is successful on the frontend
46
- const handleSignin = (service, data) => {
47
- console.log(`Signed in with ${service}:`, data);
48
- // Send 'service' and 'data' to your backend for verification
49
- fetch('/api/auth/verify', {
50
- method: 'POST',
51
- headers: { 'Content-Type': 'application/json' },
52
- body: JSON.stringify({ service, data }),
53
- })
54
- .then(res => res.json())
55
- .then(result => {
56
- if (result.success) {
57
- console.log('Backend verification successful:', result.email);
58
- // Proceed with user session, redirect, etc.
59
- } else {
60
- console.error('Backend verification failed:', result.error);
61
- }
62
- });
63
- };
64
-
65
- return (
66
- <div className="signinwith-container">
67
- <h2>Sign In</h2>
68
- <SignInWith services={services} onSignin={handleSignin} onError={error=>console.error(error)} />
69
- </div>
70
- );
30
+ // Configuration for the services you want to enable
31
+ const services = {
32
+ google: {
33
+ clientId: 'YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com',
34
+ },
35
+ meta: {
36
+ appId: 'YOUR_FACEBOOK_APP_ID',
37
+ },
38
+ apple: {
39
+ clientId: 'YOUR_APPLE_SERVICE_ID', // e.g., com.mywebsite.signin
40
+ redirectUri: '/redirect-oauth.html'
41
+ // Ensure this callback route exists or handle the popup flow appropriately.
42
+ },
43
+ discord: {
44
+ clientId: 'YOUR_DISCORD_CLIENT_ID',
45
+ redirectUri: '/redirect-oauth.html',
46
+ },
47
+ };
48
+
49
+ // Callback function when sign-in is successful on the frontend
50
+ const handleSignin = (service, data) => {
51
+ console.log(`Signed in with ${service}:`, data);
52
+ // Send 'service' and 'data' to your backend for verification
53
+ fetch('/api/auth/verify', {
54
+ method: 'POST',
55
+ headers: { 'Content-Type': 'application/json' },
56
+ body: JSON.stringify({ service, data }),
57
+ })
58
+ .then(res => res.json())
59
+ .then(result => {
60
+ if (result.success) {
61
+ console.log('Backend verification successful:', result.email);
62
+ // Proceed with user session, redirect, etc.
63
+ } else {
64
+ console.error('Backend verification failed:', result.error);
65
+ }
66
+ });
67
+ };
68
+
69
+ return (
70
+ <div className="signinwith-container">
71
+ <h2>Sign In</h2>
72
+ <SignInWith services={services} onSignin={handleSignin} onError={error=>console.error(error)} />
73
+ </div>
74
+ );
71
75
  }
72
76
 
73
77
  export default App;
@@ -75,8 +79,8 @@ export default App;
75
79
 
76
80
  ### Props for `SignInWith`
77
81
 
78
- * `services` (Object, required): An object where keys are the service names (`google`, `meta`, `apple`) and values are their respective configuration objects.
79
- * `onSignin` (Function, required): A callback function that receives `(serviceName, data)` when a sign-in attempt is successful on the client-side. `data` contains the necessary information (e.g., `credential` for Google, `accessToken` for Facebook, `authorization` object for Apple) to be sent to your backend for verification.
82
+ * `services` (Object, required): An object where keys are the service names (`google`, `meta`, `apple`, `discord`) and values are their respective configuration objects.
83
+ * `onSignin` (Function, required): A callback function that receives `(serviceName, data)` when a sign-in attempt is successful on the client-side. `data` contains the necessary information (e.g., `credential` for Google, `accessToken` for Facebook/Discord, `authorization` object for Apple) to be sent to your backend for verification.
80
84
  * `onError` (Function, optional): A callback function that receives an error string if there's an issue during the sign-in process.
81
85
 
82
86
  ## Backend Verification
@@ -94,44 +98,49 @@ app.use(express.json());
94
98
  // Your service configurations (should match frontend, plus any secrets)
95
99
  // Store these securely, e.g., in environment variables
96
100
  const servicesConfig = {
97
- google: {
98
- clientId: process.env.GOOGLE_CLIENT_ID,
99
- },
100
- facebook: {
101
- // Facebook verification only needs the access token from the frontend
102
- // No specific backend config needed here for the library function itself
103
- // but you might need App ID/Secret for other Graph API calls.
104
- },
105
- apple: {
106
- clientId: process.env.APPLE_CLIENT_ID, // Your Service ID (e.g., com.mywebsite.signin)
107
- // The library uses apple-signin-auth which might require more config
108
- // depending on how you generated your keys. Refer to its documentation.
109
- },
101
+ google: {
102
+ clientId: process.env.GOOGLE_CLIENT_ID,
103
+ },
104
+ facebook: {
105
+ // Facebook verification only needs the access token from the frontend
106
+ // No specific backend config needed here for the library function itself
107
+ // but you might need App ID/Secret for other Graph API calls.
108
+ },
109
+ apple: {
110
+ clientId: process.env.APPLE_CLIENT_ID, // Your Service ID (e.g., com.mywebsite.signin)
111
+ // The library uses apple-signin-auth which might require more config
112
+ // depending on how you generated your keys. Refer to its documentation.
113
+ },
114
+ discord: {
115
+ clientId: process.env.DISCORD_CLIENT_ID,
116
+ clientSecret: process.env.DISCORD_CLIENT_SECRET,
117
+ redirectUri: process.env.DISCORD_REDIRECT_URI,
118
+ },
110
119
  };
111
120
 
112
121
  app.post('/api/auth/verify', async (req, res) => {
113
- const { service, data } = req.body;
114
-
115
- if (!service || !data) {
116
- return res.status(400).json({ success: false, error: 'Missing service or data' });
117
- }
118
-
119
- try {
120
- const result = await verifySignin(servicesConfig, service, data);
121
- if (result.success) {
122
- // Verification successful!
123
- // Find or create user with result.email
124
- console.log(`Verified ${service} sign in for: ${result.email}`);
125
- res.json({ success: true, email: result.email });
126
- } else {
127
- // Verification failed
128
- console.error(`Failed to verify ${service}: ${result.error}`);
129
- res.status(401).json({ success: false, error: result.error || 'Verification failed' });
130
- }
131
- } catch (error) {
132
- console.error(`Error during ${service} verification:`, error);
133
- res.status(500).json({ success: false, error: 'Internal server error' });
134
- }
122
+ const { service, data } = req.body;
123
+
124
+ if (!service || !data) {
125
+ return res.status(400).json({ success: false, error: 'Missing service or data' });
126
+ }
127
+
128
+ try {
129
+ const result = await verifySignin(servicesConfig, service, data);
130
+ if (result.success) {
131
+ // Verification successful!
132
+ // Find or create user with result.email
133
+ console.log(`Verified ${service} sign in for: ${result.email}`);
134
+ res.json({ success: true, email: result.email });
135
+ } else {
136
+ // Verification failed
137
+ console.error(`Failed to verify ${service}: ${result.error}`);
138
+ res.status(401).json({ success: false, error: result.error || 'Verification failed' });
139
+ }
140
+ } catch (error) {
141
+ console.error(`Error during ${service} verification:`, error);
142
+ res.status(500).json({ success: false, error: 'Internal server error' });
143
+ }
135
144
  });
136
145
 
137
146
  const PORT = process.env.PORT || 3001;
@@ -139,19 +148,16 @@ app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
139
148
 
140
149
  ```
141
150
 
142
- ### Verification Functions
151
+ ### Verification
143
152
 
144
153
  The main `verifySignin` function delegates to service-specific functions:
145
154
 
146
- * `verifySigninGoogle(config, verificationData)`: Verifies Google ID token.
147
- * `config`: Needs `{ clientId }`.
148
- * `verificationData`: Needs `{ credential }`.
149
- * `verifySigninFacebook(config, verificationData)`: Verifies Facebook access token by fetching user email.
150
- * `config`: Not directly used by the function, but you need your App ID configured on the frontend.
151
- * `verificationData`: Needs `{ accessToken }`.
152
- * `verifySigninApple(config, verificationData)`: Verifies Apple ID token.
153
- * `config`: Needs `{ clientId }` (Your Apple Service ID).
154
- * `verificationData`: Needs `{ id_token }` (from the `authorization` object).
155
+ * `verifyGoogleToken(servicesConfig, data)`: Verifies the Google ID token against Google's servers. It checks the token's signature, expiration, and audience (client ID).
156
+ * `verifyMetaToken(servicesConfig, data)`: Verifies the Meta (Facebook) access token by calling the Facebook Graph API. It checks if the token is valid and associated with your Facebook App.
157
+ * `verifyAppleToken(servicesConfig, data)`: Verifies the Apple authorization code. It checks the code's validity and audience (client ID). It may require additional configuration depending on your Apple Developer setup (e.g., private key).
158
+ * `verifyDiscordToken(servicesConfig, data)`: Exchanges the Discord authorization code for an access token, then uses the access token to fetch user information from the Discord API. It verifies that the code is valid and associated with your Discord application.
159
+
160
+ Each of these functions returns a promise that resolves to an object with either a `success: true` and the user's `email`, or `success: false` and an `error` message.
155
161
 
156
162
  ## Styling
157
163
 
@@ -161,28 +167,84 @@ The buttons have the base class `signinwith-button` and provider-specific classe
161
167
  * `signinwith-button-google` (Note: Google button uses `renderButton`, styling might be limited)
162
168
  * `signinwith-button-meta`
163
169
  * `signinwith-button-apple`
170
+ * `signinwith-button-discord`
164
171
 
165
172
  You can override these styles in your own CSS. The container in the example uses `signinwith-container` for layout.
166
173
 
167
174
  ## Configuration Details
168
175
 
169
176
  * **Google:**
170
- * Create a project in [Google Cloud Console](https://console.cloud.google.com/).
171
- * Set up OAuth 2.0 Credentials (Web application type).
172
- * Add your domain(s) to "Authorized JavaScript origins".
173
- * Add your backend callback URL (if applicable) to "Authorized redirect URIs".
174
- * Get your **Client ID**.
177
+ * Create a project in [Google Cloud Console](https://console.cloud.google.com/).
178
+ * Set up OAuth 2.0 Credentials (Web application type).
179
+ * Add your domain(s) to "Authorized JavaScript origins".
180
+ * Add your backend callback URL (if applicable) to "Authorized redirect URIs".
181
+ * Get your **Client ID**.
175
182
  * **Facebook (Meta):**
176
- * Create an App at [Facebook for Developers](https://developers.facebook.com/).
177
- * Set up "Facebook Login for Business".
178
- * Add your domain(s) to the App Domains and Site URL in the app settings.
179
- * Get your **App ID**.
183
+ * Create an App at [Facebook for Developers](https://developers.facebook.com/).
184
+ * Set up "Facebook Login for Business".
185
+ * Add your domain(s) to the App Domains and Site URL in the app settings.
186
+ * Get your **App ID**.
180
187
  * **Apple:**
181
- * Register an App ID and a Service ID in your [Apple Developer Account](https://developer.apple.com/).
182
- * Configure "Sign in with Apple" for your App ID.
183
- * Associate your domain(s) with the Service ID and verify them.
184
- * Get your **Service ID** (used as `clientId`). You might also need a private key for certain backend operations, but `apple-signin-auth` handles basic token verification with just the `clientId` (audience check). Ensure the frontend `redirectURI` is correctly handled (either via a backend route or popup flow).
185
-
186
- ## License
188
+ * Register an App ID and a Service ID in your [Apple Developer Account](https://developer.apple.com/).
189
+ * Configure "Sign in with Apple" for your App ID.
190
+ * Associate your domain(s) with the Service ID and verify them.
191
+ * Get your **Service ID** (used as `clientId`). You might also need a private key for certain backend operations, but `apple-signin-auth` handles basic token verification with just the `clientId` (audience check). Ensure the frontend `redirectUri` is correctly handled (either via a backend route or popup flow).
192
+ * **Discord:**
193
+ * Create an App at [Discord Developer Portal](https://discord.com/developers/applications).
194
+ * Set up OAuth2.
195
+ * Add your redirect URI.
196
+ * Get your **Client ID** and **Client Secret**. Store the client secret securely on the backend.
197
+
198
+ ## Redirect URI (Popup)
199
+
200
+ For Discord and Apple, the `redirectUri` in the frontend configuration should point to a static HTML file (e.g., `redirect-oauth.html`) that handles the OAuth2 code. This file facilitates communication between the popup window and the main application window. Place the following content to be the page that is set as the redirectUri:
201
+
202
+ ```html
203
+ <!DOCTYPE html>
204
+ <html>
205
+ <head>
206
+ <title>Discord Authentication</title>
207
+ <style>
208
+ body {
209
+ font-family: sans-serif;
210
+ display: flex;
211
+ justify-content: center;
212
+ align-items: center;
213
+ height: 100vh;
214
+ margin: 0;
215
+ background-color: #f0f0f0;
216
+ }
217
+ .container {
218
+ text-align: center;
219
+ padding: 20px;
220
+ background-color: #fff;
221
+ border-radius: 8px;
222
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
223
+ }
224
+ </style>
225
+ </head>
226
+ <body>
227
+ <div class="container">
228
+ <p>You can close this window now.</p>
229
+ <script>
230
+ const urlParams = new URLSearchParams(window.location.search);
231
+ const code = urlParams.get('code');
232
+ const error = urlParams.get('error');
233
+
234
+ if (code) {
235
+ window.opener.postMessage({ type: 'discordAuth', code: code }, window.location.origin);
236
+ } else if(error) {
237
+ window.opener.postMessage({ type: 'oauthError', error: error }, window.location.origin);
238
+ }
239
+
240
+ // Attempt to close the window
241
+ setTimeout(() => {
242
+ window.close();
243
+ }, 0);
244
+ </script>
245
+ </div>
246
+ </body>
247
+ </html>
248
+ ```
187
249
 
188
- ISC
250
+ This HTML file extracts the authorization code (or error) from the URL hash and sends it back to the main window using `postMessage`. The main application needs to listen for this message and then proceed with the backend verification. This approach is necessary because Discord's OAuth flow, when initiated in a popup, requires a way to pass the authorization code back to the originating window.