7.9 KiB
7.9 KiB
OAuth2 purpose
A way for the user to tell google to give an access to myapp app
Flow:
| When | What | How |
|---|---|---|
| 1 | Get Code | Front ⇢ Google ⇢ Back |
| 2 | Exchange Code with Token | Back ⇢ Google ⇢ Back ⇢ Front |
| 4 | Use Token | Front ⇢ Back ⇢ Google ⇢ Back ⇢ Front |
Details:
-
Get Authorization Code
- Frontend Navigate to Google URL with a callback url
- Google Redirect to Backend's callback url with the authorization code
-
Exchange Code with Token
- Backend POST the
codeto Googlehttps://oauth2.googleapis.com/token - Google Response to Backend with an
access_tokenand arefresh token - Backend Redirect to Frontend
https://myapp/auth/successwith theaccess_tokenin acookie
- Backend POST the
-
Use Token
- Frontend GET profile data from Backend
https://myapp/api/auth/profileusing thecookie - Backend GET profile data from Google
https://www.googleapis.com/oauth2/v3/userinfousing theaccess_token - Google Response to Backend with profile data
- Backend Response to Frontend with profile data
- Frontend GET profile data from Backend
1. Get Code
- Frontend GET to Google
https://accounts.google.com/o/oauth2with callback url - Google 302 to Backend
https://myapp/api/auth/callbackwith authorization code
1. Front GET to Google
GET https://accounts.google.com/o/oauth2/v2/auth?
response_type=code& # This indicates you're using the "authorization code" flow.
client_id=ABC34JHS9D& # Your Google API client ID created on the google API console.
redirect_uri=https://myapp/api/auth/callback& # The URI Google will redirect to after the user consents.
scope=email%20profile& # The permissions you're requesting (e.g., email, profile).
state=xyz123 # A random string to protect against CSRF attacks.
2. Google 302 to Back
HTTP/1.1 302 Found
Location: https://myapp/api/auth/callback?code=4/0AX4XfWgyVyz-uT8k7WiyLg0Q&state=xyz123
Content-Type: text/html; charset=UTF-8
Content-Length: 0
Security: the state string should be validated upon receiving the response from Google, as it ensures that the response corresponds to the request.
2. Exchange Code with Token
- Backend POST the
codeto Googlehttps://oauth2.googleapis.com/token - Google response to Backend with an
access_tokenand arefresh token - Backend response to Frontend with the
access_tokenin acookie
1. Backend POST the code to Google
POST https://oauth2.googleapis.com/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=AAAABCX4XfWgyVyziyLg0QHHHHH&
client_id=ABC34JHS9D&
client_secret=PASS1234&
redirect_uri=https://myapp/callback
2. Google response to Backend
{
"access_token": "ya29.a0AfH6SMC8Op6zkVX-VoA",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "1//04d5lrjAqU4kS3vGdMvckw",
"scope": "email profile"
}
3. Backend Redirect to Frontend success page
This redirect will place a cookie in the browser that contains the access token
HTTP/1.1 302 Found
Location: http://localhost:3000/dashboard
Set-Cookie: access_token=ya29.a0AfH6SMC8Op6zkVX-VoA; HttpOnly; Secure; Max-Age=3600; Path=/;
Backend code
Implements the endpoint /auth/google/callback
1. Recieves authorization code from Google
2. POST send the authorization code to https://oauth2.googleapis.com/token
3. POST response the access & refresh tokens
4. Redirect to Fronend success page with a cookie contains the access token
app.get('/callback', async (req, res) => {
try {
//
// 1. Get the authorization code from Google's redirect
//
const { code } = req.query;
//
// 2. POST the authorization code to Google
//
const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
code,
client_id: process.env.GOOGLE_CLIENT_ID,
client_secret: process.env.GOOGLE_CLIENT_SECRET,
redirect_uri: 'https://myapp/callback',
grant_type: 'authorization_code',
}),
});
//
// 3. Get Response tokens from Google
//
const { access_token, refresh_token } = await tokenResponse.json();
//
// 4. Redirect to Fronend success page with a cookie contains the access token
//
res.cookie('access', access_token, {
httpOnly: true, // Cannot be accessed by client-side JavaScript
secure: true, // Only sent over HTTPS
sameSite: 'strict', // CSRF protection
maxAge: 24 * 60 * 60 * 1000, // 24 hours
});
res.redirect('https://myapp/authentication-success');
} catch (error) {
res.redirect('https://myapp/authentication-error');
}
});
3. Use Token
1. Frontend GET profile data from Backend
curl -X GET https://myapp/api/auth/profile \
-H "Cookie: access_token=ya29.a0AfH6SMC8Op6zkVX-VoA" \
-H "Accept: application/json"
2. Backend GET profile data from Google
curl -X GET "https://www.googleapis.com/oauth2/v3/userinfo" \
-H "Authorization: Bearer {access_token}" \
-H "Accept: application/json"
3. Google response to Back with profile data
{
"sub": "1234567890",
"name": "John Doe",
"given_name": "John",
"family_name": "Doe",
"profile": "https://plus.google.com/1234567890",
"picture": "https://lh3.googleusercontent.com/a-/AOh14GgIXXl5JXzW0c1Szbl-e1Jch1vhl5rHhH65vlK6J5g5PqkGjj1O0p3t8bgVEOykQ6ykFSQ=s96",
"email": "john.doe@example.com",
"email_verified": true,
"locale": "en"
}
4. Backend response to Front with profile data
{
"sub": "1234567890",
"name": "John Doe",
"picture": "https://lh3.googleusercontent.com/8bgVEOykQ6ykFSQ=s96",
"email": "john.doe@example.com",
}
Frontend Code
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const App = () => {
const [user, setUser] = useState(null);
const [isAuthenticated, setIsAuthenticated] = useState(false);
// Fetch user data from the backend's /profile endpoint
const fetchUserData = async () => {
try {
const response = await axios.get('http://localhost:3000/profile', {
withCredentials: true, // Important: Ensure cookies are sent with the request
});
setUser(response.data); // Set the user data from the response
setIsAuthenticated(true);
} catch (error) {
console.error('Error fetching user profile:', error);
}
};
useEffect(() => {
// Check if the user is authenticated by checking the cookie
if (document.cookie.includes('access_token')) {
fetchUserData(); // Fetch user profile if the access token is available in cookies
}
}, []);
return (
<div className="App">
<h1>Google OAuth2 with React</h1>
{!isAuthenticated ? (
<div>
<p>Please log in with Google to access your profile:</p>
<button onClick={() => window.location.href = 'http://localhost:3000/auth/google'}>
Login with Google
</button>
</div>
) : (
<div>
<h2>Welcome, {user?.name}</h2>
<p>Email: {user?.email}</p>
<img src={user?.picture} alt="Profile" />
</div>
)}
</div>
);
};
export default App;