signinwith 1.0.0
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.
- package/index.js +38 -0
- package/package.json +34 -0
- package/react.jsx +97 -0
- package/readme.md +187 -0
- package/styles.css +56 -0
package/index.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { OAuth2Client } from 'google-auth-library';
|
|
2
|
+
import appleSigninAuth from 'apple-signin-auth';
|
|
3
|
+
|
|
4
|
+
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();
|
|
11
|
+
return payload.email ? { success: true, email: payload.email } : { success: false, error: 'Email not found' };
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const verifySigninMeta = async (config, verificationData) => {
|
|
15
|
+
const res = await fetch(`https://graph.facebook.com/me?fields=email&access_token=${verificationData.accessToken}`);
|
|
16
|
+
const profile = await res.json();
|
|
17
|
+
return profile.email ? { success: true, email: profile.email } : { success: false, error: 'Email not available from Facebook' };
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const verifySigninApple = async (config, verificationData) => {
|
|
21
|
+
const { id_token } = verificationData;
|
|
22
|
+
const result = await appleSigninAuth.verifyIdToken(id_token, {
|
|
23
|
+
audience: config.clientId,
|
|
24
|
+
ignoreExpiration: true,
|
|
25
|
+
});
|
|
26
|
+
return result.email ? { success: true, email: result.email } : { success: false, error: 'Email not available from Apple' };
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default verifySignin = async (services, service, verificationData) => {
|
|
30
|
+
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);
|
|
34
|
+
return { success: false, error: 'Unsupported service' };
|
|
35
|
+
} catch (err) {
|
|
36
|
+
return { success: false, error: err.message || 'Unknown error' };
|
|
37
|
+
}
|
|
38
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "signinwith",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Simple and straightforward library for sign in / sign up with thirdparty oAuth services like Google, Meta, Apple...",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./index.js",
|
|
11
|
+
"./react": "./react.jsx"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"signinwith",
|
|
15
|
+
"sign",
|
|
16
|
+
"in",
|
|
17
|
+
"oauth",
|
|
18
|
+
"authjs",
|
|
19
|
+
"authentication",
|
|
20
|
+
"login",
|
|
21
|
+
"clerk",
|
|
22
|
+
"auth",
|
|
23
|
+
"auth0",
|
|
24
|
+
"meta",
|
|
25
|
+
"webauthn"
|
|
26
|
+
],
|
|
27
|
+
"author": "ybouane",
|
|
28
|
+
"license": "ISC",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"apple-signin-auth": "^2.0.0",
|
|
31
|
+
"google-auth-library": "^9.15.1",
|
|
32
|
+
"react": "^19.1.0"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/react.jsx
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
// Subcomponent: Meta (Facebook)
|
|
4
|
+
export function SignInWithMeta({ service, onSignin }) {
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
if (!window.FB) {
|
|
7
|
+
window.fbAsyncInit = function () {
|
|
8
|
+
window.FB.init({
|
|
9
|
+
appId: service.appId,
|
|
10
|
+
cookie: true,
|
|
11
|
+
xfbml: true,
|
|
12
|
+
version: 'v19.0'
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
(function (d, s, id) {
|
|
17
|
+
let js, fjs = d.getElementsByTagName(s)[0];
|
|
18
|
+
if (d.getElementById(id)) return;
|
|
19
|
+
js = d.createElement(s); js.id = id;
|
|
20
|
+
js.src = "https://connect.facebook.net/en_US/sdk.js";
|
|
21
|
+
fjs.parentNode.insertBefore(js, fjs);
|
|
22
|
+
}(document, 'script', 'facebook-jssdk'));
|
|
23
|
+
}
|
|
24
|
+
}, [service.appId]);
|
|
25
|
+
|
|
26
|
+
const handleLogin = () => {
|
|
27
|
+
window.FB.login(function (response) {
|
|
28
|
+
if (response.authResponse) {
|
|
29
|
+
onSignin('meta', { accessToken: response.authResponse.accessToken });
|
|
30
|
+
}
|
|
31
|
+
}, { scope: 'email,public_profile' });
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return <button className="signinwith-button signinwith-button-meta" onClick={handleLogin}>Continue with Facebook</button>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Subcomponent: Google
|
|
38
|
+
export function SignInWithGoogle({ service, onSignin }) {
|
|
39
|
+
let btnRef = useRef();
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const script = document.createElement('script');
|
|
42
|
+
script.src = 'https://accounts.google.com/gsi/client';
|
|
43
|
+
script.async = true;
|
|
44
|
+
script.defer = true;
|
|
45
|
+
script.onload = () => {
|
|
46
|
+
window.google.accounts.id.initialize({
|
|
47
|
+
client_id: service.clientId,
|
|
48
|
+
callback: (res) => {
|
|
49
|
+
onSignin('google', { credential: res.credential });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
window.google.accounts.id.renderButton(
|
|
53
|
+
ref.current,
|
|
54
|
+
{ theme: 'outline', size: 'large' }
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
document.body.appendChild(script);
|
|
58
|
+
}, [service.clientId]);
|
|
59
|
+
|
|
60
|
+
return <div className="signinwith-button signinwith-button-google" ref={btnRef}></div>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Subcomponent: Apple
|
|
64
|
+
export function SignInWithApple({ service, onSignin }) {
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const script = document.createElement('script');
|
|
67
|
+
script.src = 'https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js';
|
|
68
|
+
script.onload = () => {
|
|
69
|
+
window.AppleID.auth.init({
|
|
70
|
+
clientId: service.clientId,
|
|
71
|
+
scope: 'email name',
|
|
72
|
+
redirectURI: window.location.origin + '/auth/apple/callback',
|
|
73
|
+
usePopup: true,
|
|
74
|
+
});
|
|
75
|
+
window.addEventListener('AppleIDSignInOnSuccess', (e) => {
|
|
76
|
+
onSignin('apple', e.detail.authorization);
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
document.body.appendChild(script);
|
|
80
|
+
}, [service.clientId]);
|
|
81
|
+
|
|
82
|
+
return <button className="signinwith-button signinwith-button-apple" onClick={() => window.AppleID.auth.signIn()}>Continue with Apple</button>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Main SignInWith Component
|
|
86
|
+
export default function SignInWith({ onSignin, services, theme = 'light' }) {
|
|
87
|
+
return (
|
|
88
|
+
<div style={{ display: 'flex', gap: '1rem', flexDirection: 'column' }}>
|
|
89
|
+
{Object.entries(services).map(([key, config]) => {
|
|
90
|
+
if (key === 'google') return <SignInWithGoogle key={key} service={config} onSignin={onSignin} />;
|
|
91
|
+
if (key === 'meta') return <SignInWithMeta key={key} service={config} onSignin={onSignin} />;
|
|
92
|
+
if (key === 'apple') return <SignInWithApple key={key} service={config} onSignin={onSignin} />;
|
|
93
|
+
return null;
|
|
94
|
+
})}
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Sign In With
|
|
2
|
+
|
|
3
|
+
A simple and straightforward library for adding "Sign in with..." buttons (Google, Meta/Facebook, Apple) to your web application, handling both the frontend UI (React) and backend verification.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
* Easy integration for Google, Meta (Facebook), and Apple sign-in.
|
|
8
|
+
* React components for the frontend buttons.
|
|
9
|
+
* Backend utility functions to verify the identity tokens/access tokens.
|
|
10
|
+
* Basic customizable styling.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install signinwith
|
|
16
|
+
# or
|
|
17
|
+
yarn add signinwith
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Frontend Usage (React)
|
|
21
|
+
|
|
22
|
+
Import the main component and the CSS styles:
|
|
23
|
+
|
|
24
|
+
```jsx
|
|
25
|
+
import React from 'react';
|
|
26
|
+
import SignInWith from 'signinwith/react';
|
|
27
|
+
import 'signinwith/styles.css'; // Import the styles
|
|
28
|
+
|
|
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} />
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export default App;
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Props for `SignInWith`
|
|
77
|
+
|
|
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 Meta, `authorization` object for Apple) to be sent to your backend for verification.
|
|
80
|
+
|
|
81
|
+
## Backend Verification
|
|
82
|
+
|
|
83
|
+
Import the verification function on your server:
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
// Example using Express.js
|
|
87
|
+
import express from 'express';
|
|
88
|
+
import verifySignin from 'signinwith'; // Use the main export
|
|
89
|
+
|
|
90
|
+
const app = express();
|
|
91
|
+
app.use(express.json());
|
|
92
|
+
|
|
93
|
+
// Your service configurations (should match frontend, plus any secrets)
|
|
94
|
+
// Store these securely, e.g., in environment variables
|
|
95
|
+
const servicesConfig = {
|
|
96
|
+
google: {
|
|
97
|
+
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
98
|
+
},
|
|
99
|
+
meta: {
|
|
100
|
+
// Meta verification only needs the access token from the frontend
|
|
101
|
+
// No specific backend config needed here for the library function itself
|
|
102
|
+
// but you might need App ID/Secret for other Graph API calls.
|
|
103
|
+
},
|
|
104
|
+
apple: {
|
|
105
|
+
clientId: process.env.APPLE_CLIENT_ID, // Your Service ID (e.g., com.mywebsite.signin)
|
|
106
|
+
// The library uses apple-signin-auth which might require more config
|
|
107
|
+
// depending on how you generated your keys. Refer to its documentation.
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
app.post('/api/auth/verify', async (req, res) => {
|
|
112
|
+
const { service, data } = req.body;
|
|
113
|
+
|
|
114
|
+
if (!service || !data) {
|
|
115
|
+
return res.status(400).json({ success: false, error: 'Missing service or data' });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const result = await verifySignin(servicesConfig, service, data);
|
|
120
|
+
if (result.success) {
|
|
121
|
+
// Verification successful!
|
|
122
|
+
// Find or create user with result.email
|
|
123
|
+
console.log(`Verified ${service} sign in for: ${result.email}`);
|
|
124
|
+
res.json({ success: true, email: result.email });
|
|
125
|
+
} else {
|
|
126
|
+
// Verification failed
|
|
127
|
+
console.error(`Failed to verify ${service}: ${result.error}`);
|
|
128
|
+
res.status(401).json({ success: false, error: result.error || 'Verification failed' });
|
|
129
|
+
}
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error(`Error during ${service} verification:`, error);
|
|
132
|
+
res.status(500).json({ success: false, error: 'Internal server error' });
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const PORT = process.env.PORT || 3001;
|
|
137
|
+
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Verification Functions
|
|
142
|
+
|
|
143
|
+
The main `verifySignin` function delegates to service-specific functions:
|
|
144
|
+
|
|
145
|
+
* `verifySigninGoogle(config, verificationData)`: Verifies Google ID token.
|
|
146
|
+
* `config`: Needs `{ clientId }`.
|
|
147
|
+
* `verificationData`: Needs `{ credential }`.
|
|
148
|
+
* `verifySigninMeta(config, verificationData)`: Verifies Meta access token by fetching user email.
|
|
149
|
+
* `config`: Not directly used by the function, but you need your App ID configured on the frontend.
|
|
150
|
+
* `verificationData`: Needs `{ accessToken }`.
|
|
151
|
+
* `verifySigninApple(config, verificationData)`: Verifies Apple ID token.
|
|
152
|
+
* `config`: Needs `{ clientId }` (Your Apple Service ID).
|
|
153
|
+
* `verificationData`: Needs `{ id_token }` (from the `authorization` object).
|
|
154
|
+
|
|
155
|
+
## Styling
|
|
156
|
+
|
|
157
|
+
Basic styles are provided in `signinwith/styles.css`. You can import this file directly into your project.
|
|
158
|
+
|
|
159
|
+
The buttons have the base class `signinwith-button` and provider-specific classes:
|
|
160
|
+
* `signinwith-button-google` (Note: Google button uses `renderButton`, styling might be limited)
|
|
161
|
+
* `signinwith-button-meta`
|
|
162
|
+
* `signinwith-button-apple`
|
|
163
|
+
|
|
164
|
+
You can override these styles in your own CSS. The container in the example uses `signinwith-container` for layout.
|
|
165
|
+
|
|
166
|
+
## Configuration Details
|
|
167
|
+
|
|
168
|
+
* **Google:**
|
|
169
|
+
* Create a project in [Google Cloud Console](https://console.cloud.google.com/).
|
|
170
|
+
* Set up OAuth 2.0 Credentials (Web application type).
|
|
171
|
+
* Add your domain(s) to "Authorized JavaScript origins".
|
|
172
|
+
* Add your backend callback URL (if applicable) to "Authorized redirect URIs".
|
|
173
|
+
* Get your **Client ID**.
|
|
174
|
+
* **Meta (Facebook):**
|
|
175
|
+
* Create an App at [Meta for Developers](https://developers.facebook.com/).
|
|
176
|
+
* Set up "Facebook Login for Business".
|
|
177
|
+
* Add your domain(s) to the App Domains and Site URL in the app settings.
|
|
178
|
+
* Get your **App ID**.
|
|
179
|
+
* **Apple:**
|
|
180
|
+
* Register an App ID and a Service ID in your [Apple Developer Account](https://developer.apple.com/).
|
|
181
|
+
* Configure "Sign in with Apple" for your App ID.
|
|
182
|
+
* Associate your domain(s) with the Service ID and verify them.
|
|
183
|
+
* 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).
|
|
184
|
+
|
|
185
|
+
## License
|
|
186
|
+
|
|
187
|
+
ISC
|
package/styles.css
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
.signinwith-button {
|
|
2
|
+
display: inline-flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
justify-content: center;
|
|
5
|
+
padding: 10px 24px;
|
|
6
|
+
border: 1px solid #dadce0;
|
|
7
|
+
border-radius: 4px;
|
|
8
|
+
font-size: 14px;
|
|
9
|
+
font-weight: 500;
|
|
10
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
|
11
|
+
cursor: pointer;
|
|
12
|
+
text-align: center;
|
|
13
|
+
text-decoration: none;
|
|
14
|
+
min-width: 220px;
|
|
15
|
+
height: 40px;
|
|
16
|
+
box-sizing: border-box;
|
|
17
|
+
transition: background-color 0.3s ease, border-color 0.3s ease;
|
|
18
|
+
color: #3c4043;
|
|
19
|
+
background-color: #ffffff;
|
|
20
|
+
|
|
21
|
+
&:hover {
|
|
22
|
+
background-color: #f8f9fa;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
&-meta {
|
|
26
|
+
background-color: #1877F2;
|
|
27
|
+
color: white;
|
|
28
|
+
border-color: #1877F2;
|
|
29
|
+
|
|
30
|
+
&:hover {
|
|
31
|
+
background-color: #166FE5;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
&-apple {
|
|
36
|
+
background-color: #000000;
|
|
37
|
+
color: white;
|
|
38
|
+
border-color: #000000;
|
|
39
|
+
|
|
40
|
+
&:hover {
|
|
41
|
+
background-color: #333333;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
#google-signin-btn > div {
|
|
48
|
+
margin: 0 !important;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.signinwith-container {
|
|
52
|
+
display: flex;
|
|
53
|
+
gap: 1rem;
|
|
54
|
+
flex-direction: column;
|
|
55
|
+
align-items: center;
|
|
56
|
+
}
|