547 lines
15 KiB
Markdown
547 lines
15 KiB
Markdown
### OAuth2 purpose
|
||
|
||
A way for the `user` to tell `google` to give an access token to `xorismesiti.gr` app
|
||
|
||
<br><br><br>
|
||
|
||
|
||
|
||
### OAuth2 Standar Flow:
|
||
|
||
1. **User clicks** "Login with Google" on your platform `xorismesiti.gr`
|
||
2. **Authorization Request**: Redirect 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 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.
|
||
2. **Get** the authorization `code` after Google redirects back to the frontend.
|
||
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 Google (or other resources) using the `access_token`
|
||
3. **Store** the `tokens` securely (in session or a database).
|
||
4. **Refresh** the `access_token` if it expires.
|
||
|
||
<br><br><br>
|
||
|
||
|
||
|
||
|
||
|
||
# 1. [Frontend] Request Authorization code
|
||
|
||
1. The use clicks a "Login with Google" button with a URL to Google's OAuth 2.0 authorization endpoint and redirects the user there.
|
||
2. After this 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 specified redirect_uri with an authorization code.
|
||
|
||
<details>
|
||
<summary><h3>HTTP Request</h3></summary>
|
||
|
||
```sh
|
||
GET https://accounts.google.com/o/oauth2/v2/auth?
|
||
response_type=code&
|
||
client_id=YOUR_GOOGLE_CLIENT_ID&
|
||
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.
|
||
|
||
</details>
|
||
|
||
<details>
|
||
<summary><h3>Frontent Code</h3></summary>
|
||
|
||
```js
|
||
// Redirect to Google's OAuth 2.0 endpoint when user clicks login
|
||
const loginWithGoogle = () => {
|
||
const clientId = 'YOUR_GOOGLE_CLIENT_ID'; // 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
|
||
};
|
||
```
|
||
|
||
</details>
|
||
|
||
<br><br><br>
|
||
|
||
|
||
|
||
|
||
|
||
# 2. [Frontend] Receive Authorization Code
|
||
|
||
1. User grants permission
|
||
2. Google redirect the user to the `redirect_uri` you specified in the previous request (`https://xorismesiti.gr/api/auth/callback`)
|
||
3. The frontend must not directly exchange the `code` for an `access_token`. Instead, it sends the `code` to the backend via an API request.
|
||
|
||
|
||
<details>
|
||
<summary><h3>HTTP Request</h3></summary>
|
||
|
||
```bash
|
||
POST https://xorismesiti.gr/api/auth/exchange-token
|
||
Content-Type: application/json
|
||
|
||
{
|
||
"code": "authorization-code-from-google"
|
||
}
|
||
```
|
||
|
||
</details>
|
||
|
||
<details>
|
||
<summary><h3>Frontend Code</h3></summary>
|
||
|
||
```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 <div>Loading...</div>;
|
||
};
|
||
|
||
export default Callback;
|
||
```
|
||
|
||
</details>
|
||
|
||
<br><br><br>
|
||
|
||
|
||
|
||
|
||
|
||
# 3. Backend (Node.js): Handle Token Exchange
|
||
|
||
1. The backend receives the authorization `code` from the frontend,
|
||
2. The backend makes a `POST` request to Google token endpoint, to exchange the authorization `code` for the `access_token` and optionally a `refresh token`
|
||
3. The backend ensures to never expose the client_secret to the frontend. This step should always be handled on the backend.
|
||
4. The backend will exchange the `code` for an `access_token` and `refresh_token`, which are sent back to the frontend or stored securely for subsequent API calls.
|
||
|
||
<details>
|
||
<summary><h3>HTTP Request</h3></summary>
|
||
|
||
- `HTTP` Method: POST
|
||
- `URL`: https://oauth2.googleapis.com/token
|
||
- `Headers`:
|
||
- `Content`-Type: application/x-www-form-urlencoded
|
||
- `Body` Parameters:
|
||
- `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).
|
||
|
||
```sh
|
||
POST https://oauth2.googleapis.com/token
|
||
Content-Type: application/x-www-form-urlencoded
|
||
|
||
grant_type=authorization_code&
|
||
code=4/0AX4XfWgNmGZVbV7Kdr8Q9yVyzIYBnbbBdLfX39ZaE8m0w8zT8jKRLl7w-uT8k7WiyLg0Q&
|
||
redirect_uri=https://xorismesiti.gr/callback&
|
||
client_id=YOUR_GOOGLE_CLIENT_ID&
|
||
client_secret=YOUR_GOOGLE_CLIENT_SECRET
|
||
```
|
||
|
||
</details>
|
||
|
||
<details>
|
||
<summary><h3>HTTP Response</h3></summary>
|
||
|
||
```json
|
||
{
|
||
"access_token": "ya29.a0AfH6SMC8Op6zXZkHi2XITkDoOVzYXt3hTY6sny54UlWlxrnKlX5Xv78is7BEHekVX-VoA",
|
||
"token_type": "Bearer",
|
||
"expires_in": 3600,
|
||
"refresh_token": "1//04d5XHqmn6Hdy3wTf5OYDP1SyBa74zEFURjddQ2A1cFw78PY13pQyWhlD2A6XhDQtKlrjAqU4kS3vGdMvckw",
|
||
"scope": "email profile"
|
||
}
|
||
```
|
||
|
||
</details>
|
||
|
||
<details>
|
||
<summary><h3>Example Backend Code:</h3></summary>
|
||
|
||
```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');
|
||
});
|
||
```
|
||
|
||
</details>
|
||
|
||
<br><br><br>
|
||
|
||
|
||
|
||
|
||
|
||
# 4. [Frontend] Use the Access Token
|
||
|
||
Once the backend exchanges the `code` for the `access_token`,
|
||
|
||
the frontend can use it to make authenticated requests to the backend or Google APIs
|
||
|
||
<details>
|
||
<summary><h3>HTTP Request</h3></summary>
|
||
|
||
```bash
|
||
GET https://xorismesiti.gr/api/user-profile
|
||
Authorization: Bearer access-token-from-backend
|
||
```
|
||
|
||
</details>
|
||
|
||
<details>
|
||
<summary><h3>HTTP Response</h3></summary>
|
||
|
||
```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"
|
||
}
|
||
```
|
||
|
||
</details>
|
||
|
||
<details>
|
||
<summary><h3>Example Frontend Code:</h3></summary>
|
||
|
||
```js
|
||
// After receiving the token, store it in the frontend (e.g., localStorage or context)
|
||
localStorage.setItem('access_token', response.access_token);
|
||
|
||
// Use it to make authenticated API requests to the backend
|
||
fetch('/api/user-profile', {
|
||
headers: {
|
||
'Authorization': `Bearer ${localStorage.getItem('access_token')}`,
|
||
},
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
// Handle user data
|
||
})
|
||
.catch(error => {
|
||
console.error('Error fetching user profile:', error);
|
||
});
|
||
```
|
||
|
||
</details>
|
||
|
||
<br><br><br>
|
||
|
||
|
||
|
||
|
||
|
||
# 5. [Backend] Fetch User Data
|
||
|
||
If you want to fetch the user profile data (e.g., from Google), your backend can use the `access_token` to request it from Google’s user info endpoint.
|
||
|
||
<details>
|
||
<summary><h3>HTTP Request</h3></summary>
|
||
|
||
```bash
|
||
GET https://www.googleapis.com/oauth2/v3/userinfo
|
||
Authorization: Bearer access-token-from-backend
|
||
```
|
||
</details>
|
||
|
||
<details>
|
||
<summary><h3>HTTP Request</h3></summary>
|
||
|
||
```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"
|
||
}
|
||
```
|
||
|
||
</details>
|
||
|
||
<details>
|
||
<summary><h3>Example Backend Code:</h3></summary>
|
||
|
||
```js
|
||
app.get('/api/user-profile', async (req, res) => {
|
||
const accessToken = req.headers['authorization'].split(' ')[1]; // Extract token from Authorization header
|
||
|
||
try {
|
||
const response = await axios.get('https://www.googleapis.com/oauth2/v3/userinfo', {
|
||
headers: {
|
||
Authorization: `Bearer ${accessToken}`,
|
||
},
|
||
});
|
||
|
||
// Send the user profile data to the frontend
|
||
res.json(response.data);
|
||
} catch (error) {
|
||
console.error('Error fetching user profile:', error);
|
||
res.status(500).json({ error: 'Failed to fetch user profile' });
|
||
}
|
||
});
|
||
```
|
||
|
||
</details>
|
||
|
||
<br><br><br>
|
||
|
||
|
||
|
||
|
||
# 6. [Backend] Token Expiry and Refresh (Optional)
|
||
|
||
<details>
|
||
<summary><h3>Example Backend Code:</h3></summary>
|
||
|
||
```js
|
||
app.post('/api/auth/refresh-token', async (req, res) => {
|
||
const { refresh_token } = req.body;
|
||
|
||
try {
|
||
const response = await axios.post('https://oauth2.googleapis.com/token', null, {
|
||
params: {
|
||
refresh_token,
|
||
client_id: clientId,
|
||
client_secret: clientSecret,
|
||
grant_type: 'refresh_token',
|
||
},
|
||
});
|
||
|
||
res.json(response.data); // Return new access token and refresh token
|
||
} catch (error) {
|
||
console.error('Error refreshing access token:', error);
|
||
res.status(500).json({ error: 'Failed to refresh access token' });
|
||
}
|
||
});
|
||
```
|
||
|
||
</details>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
```sh
|
||
GET https://xorismesiti.gr/callback?
|
||
code=4/0AX4XfWgNmGZVbV7Kdr8Q9yVyzIYBnbbBdLfX39ZaE8m0w8zT8jKRLl7w-uT8k7WiyLg0Q&
|
||
state=xyz123
|
||
```
|
||
|
||
- `HTTP` Method: GET
|
||
- `URL`: https://xorismesiti.gr/callback
|
||
- `Parameters`:
|
||
- `code`: The authorization code sent by Google.
|
||
- `state`: The same state value sent in the original request (for CSRF protection).
|
||
|
||
|
||
# 4. Access Token Request (Exchange Authorization Code for Token)
|
||
|
||
Now your platform can use exchange the authorization code for an access token and refresh token.
|
||
|
||
|
||
|
||
|
||
|
||
# 5. Access Token Response (Google Returns Tokens)
|
||
|
||
1. Google validates the request
|
||
2. and returns a response with the access token (which can be used to access the user's Google resources)
|
||
3. and optionally, a refresh token (which can be used to refresh the access token when it expires).
|
||
|
||
```json
|
||
{
|
||
"access_token": "ya29.a0AfH6SMC8Op6zXZkHi2XITkDoOVzYXt3hTY6sny54UlWlxrnKlX5Xv78is7BEHekVX-VoA",
|
||
"token_type": "Bearer",
|
||
"expires_in": 3600,
|
||
"refresh_token": "1//04d5XHqmn6Hdy3wTf5OYDP1SyBa74zEFURjddQ2A1cFw78PY13pQyWhlD2A6XhDQtKlrjAqU4kS3vGdMvckw",
|
||
"scope": "email profile"
|
||
}
|
||
```
|
||
|
||
- HTTP Method: 200 OK
|
||
- Response Body:
|
||
- access_token: The access token used for accessing the user's resources (e.g., profile, email).
|
||
- token_type: Usually Bearer, indicating the type of token.
|
||
- expires_in: The lifetime of the access token in seconds (e.g., 3600 seconds = 1 hour).
|
||
- refresh_token: (Optional) The refresh token used to obtain a new access token when the current one expires.
|
||
- scope: The scope of access granted (e.g., email, profile).
|
||
|
||
|
||
# 6. Access Protected Resources (Fetching User Profile Data)
|
||
|
||
With the access token obtained in the previous step,
|
||
|
||
your platform can now use it to fetch the user's Google profile and email information.
|
||
|
||
The token is included in the Authorization header of the request.
|
||
|
||
|
||
- URL: https://www.googleapis.com/oauth2/v3/userinfo
|
||
- HTTP Method: GET
|
||
- Headers:
|
||
- Authorization: Bearer {access_token}: The access token obtained in step 5.
|
||
|
||
|
||
**Request:**
|
||
|
||
```sh
|
||
GET https://www.googleapis.com/oauth2/v3/userinfo
|
||
Authorization: Bearer ya29.a0AfH6SMC8Op6zXZkHi2XITkDoOVzYXt3hTY6sny54UlWlxrnKlX5Xv78is7BEHekVX-VoA
|
||
```
|
||
|
||
**Response**
|
||
|
||
```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"
|
||
}
|
||
```
|
||
|
||
|
||
# 7. Refreshing the Access Token (If Necessary)
|
||
|
||
If the access token expires,
|
||
|
||
your platform can use the refresh token (if provided) to obtain a new access token without requiring the user to log in again.
|
||
|
||
- `URL`: https://oauth2.googleapis.com/token
|
||
- `HTTP` Method: POST
|
||
- `Headers`:
|
||
- `Content`-Type: application/x-www-form-urlencoded
|
||
- `Body` Parameters:
|
||
- `grant_type`=refresh_token: This indicates the refresh token flow.
|
||
- `refresh_token`: The refresh token obtained in step 5.
|
||
- `client_id`: Your Google API client ID.
|
||
- `client_secret`: Your Google API client secret.
|
||
|
||
**Request**
|
||
|
||
```sh
|
||
POST https://oauth2.googleapis.com/token
|
||
Content-Type: application/x-www-form-urlencoded
|
||
|
||
grant_type=refresh_token&
|
||
refresh_token=1//04d5XHqmn6Hdy3wTf5OYDP1SyBa74zEFURjddQ2A1cFw78PY13pQyWhlD2A6XhDQtKlrjAqU4kS3vGdMvckw&
|
||
client_id=YOUR_GOOGLE_CLIENT_ID&
|
||
client_secret=YOUR_GOOGLE_CLIENT_SECRET
|
||
```
|
||
|
||
**Response**
|
||
|
||
```json
|
||
{
|
||
"access_token": "ya29.a0AfH6SMC8Op6zXZkHi2XITkDoOVzYXt3hTY6sny54UlWlxrnKlX5Xv78is7BEHekVX-VoA",
|
||
"token_type": "Bearer",
|
||
"expires_in": 3600
|
||
}
|
||
```
|