### OAuth2 purpose
A way for the `user` to tell `google` to give an access token to `xorismesiti.gr` app
### OAuth2 Standar Flow:
1. **User** clicks button "Login with Google" on your platform `xorismesiti.gr`
2. **Authorization Request**: Button redirects user to Google's authorization endpoint `accounts.google.com/o/oauth2`
3. **User Login and Consent**: User login to Google and grants permissions.
4. **Authorization Code Response**: Google redirects user back to your app `xorismesiti.gr/callback` with an authorization `code`.
5. **Access Token Request**: App exchanges the authorization `code` for an `access_token`.
6. **Access Protected Resources**: App uses the `access_token` to fetch the user's Google profile and email from `googleapis.com/oauth2`
7. **Token Refresh** (Optional): If the `access_token` expires, app uses the `refresh token` to get a new `access_token`.
### OAuth2 Frontend/Backend Flow:
**Frontend**
1. **Redirect** the user to Google's OAuth authorization endpoint `accounts.google.com/o/oauth2`
2. **Get** the authorization `code` after Google redirects back to the frontend `xorismesiti.gr/callback`
3. **Send** the authorization `code` to the backend for `token` exchange.
**Backend**
1. **exchange** the authorization `code` for an `access_token` and `refresh token`
2. **fetch** user profile data from from `googleapis.com/oauth2` using the `access_token`
3. **Store** the `tokens` securely in session (front) or a database (back)
4. **Refresh** the `access_token` if it expires
# 1. Request Authorization code
### Frontend ⇢ Google ⇢ Frontend
1. A button "Login with Google" redirects the user to the Google's authorization endpoint `accounts.google.com/o/oauth2/v2/auth`
2. After the redirection, the user will log in to Google and grant permissions (if they haven’t already).
3. Google will redirect the user back to your redirect_uri `https://xorismesiti.gr/callback` with an authorization code `?code=`
*Security: the state string should be validated upon receiving the response from Google, as it ensures that the response corresponds to the request.*
Frontend HTTP GET Request to Google
```sh
GET https://accounts.google.com/o/oauth2/v2/auth?
response_type=code&
client_id=ABC34JHS9D&
redirect_uri=https://xorismesiti.gr/callback&
scope=email%20profile&
state=xyz123
```
- `response_type=code`: This indicates you're using the "authorization code" flow.
- `client_id`: Your Google API client ID. Created by the developers of the `xorismesiti.gr` on the google API console.
- `redirect_uri`: The URI Google will redirect to after the user consents.
- `scope`: The permissions you're requesting (e.g., email, profile).
- `state`: A random string to protect against CSRF attacks.
Google HTTP Response to Frontend
```bash
HTTP/1.1 302 Found
Location: https://xorismesiti.gr/api/auth/callback?code=4/0AX4XfWgyVyz-uT8k7WiyLg0Q&state=xyz123
Content-Type: text/html; charset=UTF-8
Content-Length: 0
```
Frontend Code
```js
// Redirect to Google's OAuth 2.0 endpoint when user clicks login
const loginWithGoogle = () => {
const clientId = 'ABC34JHS9D'; // Replace with your actual Google Client ID
const redirectUri = 'https://xorismesiti.gr/api/auth/callback'; // Backend URL where Google will send the user after login
const scope = 'email profile'; // Scopes you're requesting (email, profile, etc.)
const state = 'random-state-value'; // For CSRF protection
const googleAuthUrl = `https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scope}&state=${state}`;
window.location.href = googleAuthUrl; // Redirect user to Google
};
```
# 2. Exchange Code with Token
### Frontend ⇢ Backend ⇢ Google ⇢ Backend ⇢ Frontend
#### 2.1 Frontend
Now that the frontend has the Authorization `code` on th callback url https://xorismesiti.gr/api/auth/callback`?code=AAAABCX4XfWgyVyziyLg0QHHHHH` it can send it to the backend with POST to `xorismesiti.gr/api/auth/exchange-token`, in order the backend to exchange the `code` for an `access_token` and optionally an `refresh_token`
Frontend HTTP POST Request to Backend
```bash
POST https://xorismesiti.gr/api/auth/exchange-token
Content-Type: application/json
{
"code": "AAAABCX4XfWgyVyziyLg0QHHHHH"
}
```
#### 2.2 Backend
1. The backend **receives** the authorization `code` form frontend (Frontend POST at `xorismesiti.gr/api/auth/exchange-token`)
2. The backend **POST** Authorization `code` to Google API
3. The Google API respond to backend POST with the tokens `access_token` and `refresh_token`
4. The backend **respond** to frontend with the tokens (respond to frontend POST at `xorismesiti.gr/api/auth/exchange-token`)
*Security: The backend never expose the client_secret to the frontend. This step should always be handled on the backend.*
Backend HTTP POST Request to Google
```sh
POST https://oauth2.googleapis.com/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=AAAABCX4XfWgyVyziyLg0QHHHHH&
redirect_uri=https://xorismesiti.gr/callback&
client_id=ABC34JHS9D&
client_secret=PASS1234
```
- `HTTP Method`: POST
- `URL`: https://oauth2.googleapis.com/token
- Headers
- `Content-Type`: application/x-www-form-urlencoded
- Body
- `grant_type`=authorization_code: This specifies the grant type.
- `code`: The authorization code you received in the previous step.
- `redirect_uri`: The same redirect URI used in the authorization request.
- `client_id`: Your Google API client ID.
- `client_secret`: Your Google API client secret (which should be kept secure).
Google HTTP Response Backend
```json
{
"access_token": "ACCESSTOKEN.6zXZkHi2XITkDoOV.ACCESSTOKEN",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "1//04d5XHqmn6Hdy3wTf5OYDP1SyBa74zEFURjddQ2A1cFw78PY13pQyWhlD2A6XhDQtKlrjAqU4kS3vGdMvckw",
"scope": "email profile"
}
```
Backend Code:
```js
const express = require('express');
const axios = require('axios');
const app = express();
app.use(express.json()); // To parse JSON bodies
const clientId = 'YOUR_GOOGLE_CLIENT_ID'; // Google Client ID
const clientSecret = 'YOUR_GOOGLE_CLIENT_SECRET'; // Google Client Secret
const redirectUri = 'https://xorismesiti.gr/api/auth/callback'; // Must match the one used in frontend
// Handle token exchange
app.post('/api/auth/exchange-token', async (req, res) => {
const { code } = req.body;
try {
const response = await axios.post('https://oauth2.googleapis.com/token', null, {
params: {
code,
client_id: clientId,
client_secret: clientSecret,
redirect_uri: redirectUri,
grant_type: 'authorization_code',
},
});
const { access_token, refresh_token, expires_in } = response.data;
// Optionally, store the tokens in a secure location (e.g., session, database)
// For now, send them back to the frontend (not recommended for production)
res.json({ access_token, refresh_token, expires_in });
} catch (error) {
console.error('Error exchanging authorization code for token:', error);
res.status(500).json({ error: 'Failed to exchange authorization code for access token' });
}
});
// Start the server
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
```
Backend HTTP Response to Frontend
```json
{
"access_token": "ya29.a0AfH6SMC8Op6zXZkHi2XITkDoOVzYXt3hTY6sny54UlWlxrnKlX5Xv78is7BEHekVX-VoA",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "1//04d5XHqmn6Hdy3wTf5OYDP1SyBa74zEFURjddQ2A1cFw78PY13pQyWhlD2A6XhDQtKlrjAqU4kS3vGdMvckw",
"scope": "email profile"
}
```
Frontend Code
```js
// On the backend callback URL, the frontend receives the authorization code
import { useEffect } from 'react';
import { useRouter } from 'next/router';
const Callback = () => {
const router = useRouter();
useEffect(() => {
const { code, state } = router.query;
// Send the authorization code to the backend for token exchange
fetch('/api/auth/exchange-token', {
method: 'POST',
body: JSON.stringify({ code }),
headers: {
'Content-Type': 'application/json',
},
})
.then(response => response.json())
.then(data => {
// Handle success (store token, update UI, etc.)
console.log(data); // Typically, you'll store the access token here or manage the user session.
router.push('/dashboard'); // Redirect the user to their dashboard or home page.
})
.catch(error => {
console.error('Error exchanging token:', error);
});
}, []);
return Loading...
;
};
export default Callback;
```
#### 2.3 Front
# 4. Use the Token
### Frontend ⇢ Google ⇢ Frontend
1. The frontend receives the tokens from the Backend response,
2. Store them in the localStorage of the browser
3. Make authenticated requests directly to Google API
Frontend HTTP GET Request to Backend
```bash
GET https://www.googleapis.com/oauth2/v3/userinfo
Authorization: Bearer ACCESSTOKEN6zXZkHi2XITkDoOVACCESSTOKEN
```
Backedn HTTP Response to Frontend
```json
{
"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"
}
```
Frontend Code
```js
const fetchUserData = async (accessToken) => {
try {
const response = await fetch('https://www.googleapis.com/oauth2/v3/userinfo', {
method: 'GET',
headers: {
'Authorization': `Bearer ${accessToken}`,
},
});
if (!response.ok) {
throw new Error('Failed to fetch user data from Google');
}
// Parse the response to get the user data
const userData = await response.json();
console.log('User Data:', userData);
// Example user data:
// {
// "sub": "1234567890123456789012345678901234567890",
// "name": "John Doe",
// "given_name": "John",
// "family_name": "Doe",
// "email": "john.doe@example.com",
// "picture": "https://example.com/profile.jpg"
// }
} catch (error) {
console.error('Error fetching user data:', error);
}
};
```
# 5. Refresh the Token
### Frontend ⇢ Backend ⇢ Google ⇢ Backend ⇢ Frontend
If the access token is expired, the frontend will receive an error response from Google when attempting to fetch user data
Frontend HTTP GET Request to Google
```bash
GET https://www.googleapis.com/oauth2/v3/userinfo
Authorization: Bearer ACCESSTOKEN6zXZkHi2XITkDoOVACCESSTOKEN
```
Google HTTP Response to Frontend
```json
{
"error": "invalid_token",
"error_description": "The access token expired"
}
```
Frontend HTTP POST Refresh token to Backend
```bash
```
Frontend Code
```js
const refreshAccessToken = async (refreshToken) => {
try {
const response = await fetch('/api/refresh-token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ refresh_token: refreshToken }),
});
if (!response.ok) {
throw new Error('Failed to refresh access token');
}
const data = await response.json();
return data.access_token; // New access token
} catch (error) {
console.error('Error refreshing access token:', error);
}
};
```
Frontend Code
```js
const response = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
client_id: 'YOUR_GOOGLE_CLIENT_ID',
client_secret: 'YOUR_GOOGLE_CLIENT_SECRET',
refresh_token: refreshToken,
grant_type: 'refresh_token',
}),
});
const data = await response.json();
const newAccessToken = data.access_token; // New access token
```