dev/OAuth2-Backend-Approach.md

328 lines
9.1 KiB
Markdown

### OAuth2 purpose
A way for the `user` to tell `google` to give an access to `xorismesiti.gr` app
<br>
### 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 `|
<br>
### Details:
1. Get Code
1. Frontend **GET** to Google `https://accounts.google.com/o/oauth2` with callback url
2. Google **302** to Backend `https://xorismesiti.gr/api/auth/callback` with authorization code
2. Exchange Code with Token
1. Backend **POST** the `code` to Google `https://oauth2.googleapis.com/token`
2. Google **response** to Backend with an `access_token` and a `refresh token`
3. Backend **response** to Frontend with the `access_token` in a `cookie`
3. Use Token
1. Frontend **GET** profile data from Backend `https://xorismesiti.gr/api/auth/profile` using the `cookie`
2. Backend **GET** profile data from Google `https://www.googleapis.com/oauth2/v3/userinfo` using the `access_token` from Frontend `cookie`
3. Google **response** to Backend with profile data
4. Backend **response** to Frontend with profile data
<br><br><br>
# 1. Get Code
1. Frontend **GET** to Google `https://accounts.google.com/o/oauth2` with callback url
2. Google **302** to Backend `https://xorismesiti.gr/api/auth/callback` with authorization code
### 1. Front **GET** to Google
```sh
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://xorismesiti.gr/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
```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
```
*Security: the state string should be validated upon receiving the response from Google, as it ensures that the response corresponds to the request.*
<br><br><br>
# 2. Exchange Code with Token
1. Backend **POST** the `code` to Google `https://oauth2.googleapis.com/token`
2. Google **response** to Backend with an `access_token` and a `refresh token`
3. Backend **response** to Frontend with the `access_token` in a `cookie`
### 1. Backend **POST** the `code` to Google
```sh
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://xorismesiti.gr/callback
```
### 2. Google **response** to Backend
```json
{
"access_token": "ya29.a0AfH6SMC8Op6zXZkHi2XITkDoOVzYXt3hTY6sny54UlWlxrnKlX5Xv78is7BEHekVX-VoA",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "1//04d5XHqmn6Hdy3wTf5OYDP1SyBa74zEFURjddQ2A1cFw78PY13pQyWhlD2A6XhDQtKlrjAqU4kS3vGdMvckw",
"scope": "email profile"
}
```
### 3. Backend **response** to Frontend (for the initial request to Google)
```json
{
"access_token": "ya29.a0AfH6SMC8Op6zXZkHi2XITkDoOVzYXt3hTY6sny54UlWlxrnKlX5Xv78is7BEHekVX-VoA",
"token_type": "Bearer",
"expires_in": 3600,
}
```
<details>
<summary>
<h4>Backend code</h4>
<br><br>
<p>Implements the endpoint /auth/google/callback</p>
<br>
<ul>
<li>Recieves authorization code from Google</li>
<li>POST send the authorization code to https://oauth2.googleapis.com/token</li>
<li>POST response the access & refresh tokens</li>
<li>Respond the Fronend initial request with a cookie</li>
</ul>
</summary>
```js
// Backend callback URL: http://localhost:3000/auth/google/callback
app.get('/auth/google/callback', async (req, res) => {
const { code } = req.query; // Extract the code from the query string
try {
// Exchange the authorization code for access token and refresh token
const response = await axios.post('https://oauth2.googleapis.com/token', null, {
params: {
code,
client_id: clientId,
client_secret: clientSecret,
redirect_uri: redirectUri, // Same as in Step 1
grant_type: 'authorization_code',
},
});
const { access_token, refresh_token, expires_in } = response.data;
// Store tokens in session or cookies
res.cookie('access_token', access_token, { httpOnly: true, secure: true, maxAge: expires_in * 1000 });
res.cookie('refresh_token', refresh_token, { httpOnly: true, secure: true });
// Respond to frontend or redirect as needed
res.send('Authentication successful! You can now use the app.');
} catch (error) {
console.error('Error exchanging code for tokens:', error);
res.status(500).send('Error during authentication');
}
});
```
</details>
<br><br><br>
# 3. Use Token
### 1. Frontend **GET** profile data from Backend
```bash
curl -X GET https://xorismesiti.gr/api/auth/profile \
-H "Cookie: access_token=ya29.a0AfH6SMC8Op6zXZkHi2XITkDoOVzYXt3hTY6sny54UlWlxrnKlX5Xv78is7BEHekVX-VoA" \
-H "Accept: application/json"
```
### 2. Backend **GET** profile data from Google
```bash
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
```js
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;
```
-------------------------
```bash
GET https://xorismesiti.gr/api/user-profile
Authorization: Bearer access-token-from-backend
```
```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"
}
```
```sh
GET https://www.googleapis.com/oauth2/v3/userinfo
Authorization: Bearer ya29.a0AfH6SMC8Op6zXZkHi2XITkDoOVzYXt3hTY6sny54UlWlxrnKlX5Xv78is7BEHekVX-VoA
```
```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"
}
```
```json
{
"access_token": "ya29.a0AfH6SMC8Op6zXZkHi2XITkDoOVzYXt3hTY6sny54UlWlxrnKlX5Xv78is7BEHekVX-VoA",
"token_type": "Bearer",
"expires_in": 3600
}
```