From 22e5b705e3beb8117db14d3f78ccbe0aaa671486 Mon Sep 17 00:00:00 2001 From: sosokker Date: Thu, 2 Nov 2023 00:52:39 +0700 Subject: [PATCH] Update Use Authorization code to exchange with token. --- backend/core/settings.py | 7 +- backend/users/urls.py | 2 +- backend/users/views.py | 72 +++++++++++++++---- frontend/src/App.jsx | 3 +- frontend/src/api/axiosapi.jsx | 2 +- frontend/src/components/Home.jsx | 11 +++ frontend/src/components/Nav/Navbar.jsx | 3 +- .../authentication/AuthenticationPage.jsx | 11 ++- 8 files changed, 85 insertions(+), 26 deletions(-) create mode 100644 frontend/src/components/Home.jsx diff --git a/backend/core/settings.py b/backend/core/settings.py index f06c5ac..c795b7c 100644 --- a/backend/core/settings.py +++ b/backend/core/settings.py @@ -79,11 +79,14 @@ REST_FRAMEWORK = { REST_USE_JWT = True +GOOGLE_CLIENT_ID = config('GOOGLE_CLIENT_ID', default='fake-client-id') +GOOGLE_CLIENT_SECRET = config('GOOGLE_CLIENT_SECRET', default='fake-client-secret') + SOCIALACCOUNT_PROVIDERS = { 'google': { 'APP': { - 'client_id': config('GOOGLE_CLIENT_ID', default='fake-client-id'), - 'secret': config('GOOGLE_CLIENT_SECRET', default='fake-client-secret'), + 'client_id': GOOGLE_CLIENT_ID, + 'secret': GOOGLE_CLIENT_SECRET, 'key': '' }, "SCOPE": [ diff --git a/backend/users/urls.py b/backend/users/urls.py index fd03be1..28f77da 100644 --- a/backend/users/urls.py +++ b/backend/users/urls.py @@ -9,5 +9,5 @@ urlpatterns = [ path('token/custom_obtain/', ObtainTokenPairWithCustomView.as_view(), name='token_create_custom'), path('hello/', GreetingView.as_view(), name='hello_world'), path('dj-rest-auth/google/', GoogleLogin.as_view(), name="google_login"), - path('auth/google/', GoogleRetrieveUserInfo.as_view()) + path('auth/google/', GoogleRetrieveUserInfo.as_view()), ] \ No newline at end of file diff --git a/backend/users/views.py b/backend/users/views.py index cb63de2..3ffb3e9 100644 --- a/backend/users/views.py +++ b/backend/users/views.py @@ -3,6 +3,7 @@ import json import requests +from django.conf import settings from django.contrib.auth.hashers import make_password from rest_framework import status @@ -16,6 +17,8 @@ from allauth.socialaccount.providers.oauth2.client import OAuth2Client from dj_rest_auth.registration.views import SocialLoginView +from google_auth_oauthlib.flow import InstalledAppFlow + from .serializers import MyTokenObtainPairSerializer, CustomUserSerializer from .managers import CustomAccountManager from .models import CustomUser @@ -96,17 +99,28 @@ class GoogleRetrieveUserInfo(APIView): Retrieve user information from Google and create a user if not exists. """ permission_classes = (AllowAny,) + client_config = {"web":{"client_id": settings.GOOGLE_CLIENT_ID, + "project_id":"turtask","auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_secret": settings.GOOGLE_CLIENT_SECRET, + } + } + scopes = [ + 'https://www.googleapis.com/auth/userinfo.email', + 'https://www.googleapis.com/auth/userinfo.profile', + 'https://www.googleapis.com/auth/calendar.readonly', + ] def post(self, request): - access_token = request.data.get("token") - - user_info = self.get_google_user_info(access_token) - - if 'error' in user_info: - error_message = 'Wrong Google token or the token has expired.' - return Response({'message': error_message, 'error': user_info['error']}) - - user = self.get_or_create_user(user_info) + code = request.data.get("code") + payload = self.exchange_authorization_code(code=code) + if 'error' in payload: + return Response({'error': payload['error']}) + user_info = self.call_google_api(api_url='https://www.googleapis.com/oauth2/v2/userinfo?alt=json', + access_token=payload['access_token']) + payload['email'] = user_info['email'] + user = self.get_or_create_user(payload) token = RefreshToken.for_user(user) response = { @@ -117,13 +131,32 @@ class GoogleRetrieveUserInfo(APIView): return Response(response) - def get_google_user_info(self, access_token): - url = 'https://www.googleapis.com/oauth2/v2/userinfo' - payload = {'access_token': access_token} - response = requests.get(url, params=payload) + def get(self, request): + """Get authorization url.""" + flow = InstalledAppFlow.from_client_config(client_config=self.client_config, + scopes=self.scopes) + flow.redirect_uri = 'http://localhost:5173/' + authorization_url, state = flow.authorization_url( + access_type='offline', + # include_granted_scopes='true', + ) + return Response({'url': authorization_url}) + + def exchange_authorization_code(self, code): + """Exchange authorization code for access, id, refresh token.""" + url = 'https://oauth2.googleapis.com/token' + payload = { + 'code': code, + 'client_id': settings.GOOGLE_CLIENT_ID, + 'client_secret': settings.GOOGLE_CLIENT_SECRET, + 'redirect_uri': 'postmessage', + 'grant_type': 'authorization_code', + } + response = requests.post(url, data=payload) return json.loads(response.text) def get_or_create_user(self, user_info): + """Get or create a user based on email.""" try: user = CustomUser.objects.get(email=user_info['email']) except CustomUser.DoesNotExist: @@ -132,4 +165,15 @@ class GoogleRetrieveUserInfo(APIView): user.password = make_password(CustomAccountManager().make_random_password()) user.email = user_info['email'] user.save() - return user \ No newline at end of file + return user + + def call_google_api(self, api_url, access_token): + """Call Google API with access token.""" + headers = { + 'Authorization': f'Bearer {access_token}' + } + + response = requests.get(api_url, headers=headers) + if response.status_code == 200: + return response.json() + raise Exception('Google API Error', response) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 8c7d50d..7333c4a 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -6,6 +6,7 @@ import IconSideNav from './components/IconSideNav'; import AuthenticantionPage from './components/authentication/AuthenticationPage'; import SignUpPage from './components/authentication/SignUpPage'; import NavBar from './components/Nav/Navbar'; +import Home from './components/Home'; const App = () => { @@ -14,7 +15,7 @@ const App = () => {
-

This is Home page!

} /> + }/> }/> }/> }/> diff --git a/frontend/src/api/axiosapi.jsx b/frontend/src/api/axiosapi.jsx index 211cf14..313d6f1 100644 --- a/frontend/src/api/axiosapi.jsx +++ b/frontend/src/api/axiosapi.jsx @@ -67,7 +67,7 @@ const googleLogin = async (token) => { let res = await axios.post( "http://localhost:8000/api/auth/google/", { - token: token, + code: token, } ); // console.log('service google login res: ', res); diff --git a/frontend/src/components/Home.jsx b/frontend/src/components/Home.jsx new file mode 100644 index 0000000..3089df5 --- /dev/null +++ b/frontend/src/components/Home.jsx @@ -0,0 +1,11 @@ +import React from 'react'; + +function HomePage() { + return ( +
+

Welcome to My Website

+
+ ); +} + +export default HomePage; diff --git a/frontend/src/components/Nav/Navbar.jsx b/frontend/src/components/Nav/Navbar.jsx index 331ab9e..85ec720 100644 --- a/frontend/src/components/Nav/Navbar.jsx +++ b/frontend/src/components/Nav/Navbar.jsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Link } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import IsAuthenticated from '../authentication/IsAuthenticated'; import axiosapi from '../../api/axiosapi'; import AppBar from '@mui/material/AppBar'; @@ -28,6 +28,7 @@ const settings = { }; function NavBar() { + const Navigate = useNavigate(); const [anchorElNav, setAnchorElNav] = React.useState(null); const [anchorElUser, setAnchorElUser] = React.useState(null); diff --git a/frontend/src/components/authentication/AuthenticationPage.jsx b/frontend/src/components/authentication/AuthenticationPage.jsx index 791cdd9..a117889 100644 --- a/frontend/src/components/authentication/AuthenticationPage.jsx +++ b/frontend/src/components/authentication/AuthenticationPage.jsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useGoogleLogin } from '@react-oauth/google'; - +import axios from 'axios'; import Avatar from '@mui/material/Avatar'; import Button from '@mui/material/Button'; import CssBaseline from '@mui/material/CssBaseline'; @@ -91,12 +91,11 @@ export default function SignInSide() { } const googleLoginImplicit = useGoogleLogin({ - // flow: 'auth-code', - onSuccess: async (response) => { - console.log(response); - + flow: 'auth-code', + redirect_uri: 'postmessage', + onSuccess: async (response) => { try { - const loginResponse = await axiosapi.googleLogin(response.access_token); + const loginResponse = await axiosapi.googleLogin(response.code); if (loginResponse && loginResponse.data) { const { access_token, refresh_token } = loginResponse.data;