I am creating a basic Authentication register/login with FastAPI. However, after the user has succesfully registered and logged in, the token does not get recognized. It works fine using the "/docs" through Swagger UI, but not from the main app.
Here is my code: main.py
import uvicorn
from fastapi import Depends, HTTPException
from auth import AuthHandler
from fastapi import FastAPI, Request, Form
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
auth_handler = AuthHandler()
users = []
#app.get('/', response_class=HTMLResponse)
def get_register_form(request: Request):
return templates.TemplateResponse("register.html", {"request": request})
#app.post('/', response_class=HTMLResponse)
def register(request: Request, username: str = Form(...), password: str = Form(...)):
if len(users) != 0:
for x in users:
if x['username'] == username:
print('Username is taken!')
raise HTTPException(status_code=400, detail='Username is taken!')
hashed_password = auth_handler.get_password_hash(password)
users.append({
'username': username,
'password': hashed_password
})
print('User:', username, 'registered!')
return templates.TemplateResponse("success.html", {"request": request})
#app.get('/login', response_class=HTMLResponse)
def get_login_form(request: Request):
return templates.TemplateResponse("login.html", {"request": request})
#app.post('/login')
def login(request: Request, username: str = Form(...), password: str = Form(...)):
user = None
for x in users:
if x['username'] == username:
user = x
break
if (user is None) or (not auth_handler.verify_password(password, user['password'])):
print('Invalid username and/or password!')
raise HTTPException(status_code=401, detail='Invalid username and/or password!')
token = auth_handler.encode_token(user['username'])
return {'token': token}
#app.get('/protected')
def protected(username=Depends(auth_handler.auth_wrapper)):
return {'name': username}
if __name__ == '__main__':
uvicorn.run(app)
Here is my code: auth.py
import jwt
from fastapi import HTTPException, Security
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from passlib.context import CryptContext
from datetime import datetime, timedelta
class AuthHandler():
security = HTTPBearer()
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
secret = 'SECRET'
def get_password_hash(self, password):
return self.pwd_context.hash(password)
def verify_password(self, plain_password, hashed_password):
return self.pwd_context.verify(plain_password, hashed_password)
def encode_token(self, user_id):
payload = {
'exp': datetime.utcnow() + timedelta(days=0, minutes=5),
'iat': datetime.utcnow(),
'sub': user_id
}
return jwt.encode(
payload,
self.secret,
algorithm='HS256'
)
def decode_token(self, token):
try:
payload = jwt.decode(token, self.secret, algorithms=['HS256'])
return payload['sub']
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail='Signature has expired')
except jwt.InvalidTokenError as e:
raise HTTPException(status_code=401, detail='Invalid token')
def auth_wrapper(self, auth: HTTPAuthorizationCredentials = Security(security)):
return self.decode_token(auth.credentials)
Here is my forms.html: register.html and login.html are the same.
<!doctype html>
<html lang="en">
<head>
<link rel="stylesheet" href="../static/styles.css">
<title>Document</title>
</head>
<body>
<div id="form">
<form method="post">
<h3>Login</h3>
<label for="username">Username:</label><br>
<input type="text" name="username" id="username"><br>
<label for="password">Password:</label><br>
<input type="text" name="password" id="password"><br><br>
<input type="submit" value="Submit" id="sub">
</form>
</div>
</body>
</html>
The error I get when going to 127.0.0.1/protected is:
{"detail":"Not authenticated"}
How can I fix this, so that it recognizes the token from the user just like in docs?
I found a solution on the advise from #MatsLindh, I also simplified a lot the code, imports, etc.
You will obviously still need to have the html files with the form and fields you'll need. In my case I just added the email and password.
Please note that this is not "best practice" especially inserting in the same table of the database the password even it it's hashed.
import uvicorn
import sqlite3
import jwt
from datetime import datetime, timedelta
from fastapi import FastAPI, Form, HTTPException, Cookie, Request, Depends
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from passlib.context import CryptContext
connection = sqlite3.connect("users.db", check_same_thread=False)
cursor = connection.cursor()
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
class AuthHandler:
secret = 'SECRET' # I can put any key I want, it's going to be used to encrypt and decrypt
bcrypt_obj = CryptContext(schemes=["bcrypt"], deprecated="auto")
def get_password_hash(self, password):
return self.bcrypt_obj.hash(password)
def verify_password(self, plain_password, hashed_password):
return self.bcrypt_obj.verify(plain_password, hashed_password)
def encode_token(self, user_id):
payload = {
'exp': datetime.utcnow() + timedelta(days=0, minutes=5),
'iat': datetime.utcnow(),
'sub': user_id
}
return jwt.encode(
payload,
self.secret,
algorithm='HS256'
)
def decode_token(self, token):
try:
payload = jwt.decode(token, self.secret, algorithms=['HS256'])
return payload['sub']
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail='Signature has expired')
except jwt.InvalidTokenError as e:
raise HTTPException(status_code=401, detail='Invalid token')
def grant_access(self, token, users_db):
for x in users_db:
if x['email'] == self.decode_token(token):
return True
else:
return False
auth_handler = AuthHandler()
#app.get('/', response_class=HTMLResponse)
def get_register_form(request: Request):
return templates.TemplateResponse("register.html", {"request": request})
#app.post('/', response_class=HTMLResponse)
def register(email: str = Form(...), password: str = Form(...)):
cursor.execute("select * from users where email=:e", {"e": email})
user_with_same_email_list = cursor.fetchall()
if len(user_with_same_email_list) != 0:
print(user_with_same_email_list)
print('Email already registered!')
raise HTTPException(status_code=400, detail='Email already registered!')
else:
hashed_password = auth_handler.get_password_hash(password)
sqlite_insert_query = "INSERT INTO users (user_id, username, email, hashed_password, eoa) VALUES " \
"(1,'','" + email + "','" + hashed_password + "','')"
cursor.execute(sqlite_insert_query)
connection.commit()
print('User with email:', email, 'registered!')
response = RedirectResponse(url="/login")
response.status_code = 302
return response
#app.get('/login', response_class=HTMLResponse)
def get_login_form(request: Request):
return templates.TemplateResponse("login.html", {"request": request})
#app.post('/login')
def login(email: str = Form(...), password: str = Form(...)):
cursor.execute("select * from users where email=:e", {"e": email})
user_with_same_email_list = cursor.fetchall()
if len(user_with_same_email_list) == 0:
print('No user with this email!')
raise HTTPException(status_code=400, detail='No user with this email!')
elif (email is None) or (not auth_handler.verify_password(password, user_with_same_email_list[0][3])):
print('Invalid email and/or password!')
raise HTTPException(status_code=401, detail='Invalid email and/or password!')
else:
token = auth_handler.encode_token(email)
response = RedirectResponse(url="/check_cookie")
response.status_code = 302
response.set_cookie(key="Authorization", value=token, secure=True, httponly=True)
return response
#app.get("/check_cookie")
async def check_cookie(Authorization: str | None = Cookie(None)):
if Authorization:
email = auth_handler.decode_token(Authorization)
cursor.execute("select * from users where email=:e", {"e": email})
user_with_same_email_list = cursor.fetchall()
if len(user_with_same_email_list) == 0:
print('Invalid token')
raise HTTPException(status_code=401, detail='Invalid token')
else:
print('Access granted!')
response = RedirectResponse(url="/protected")
response.status_code = 302
return response
else:
print("No token found")
raise HTTPException(status_code=401, detail='No token found')
#app.get('/protected', response_class=HTMLResponse)
async def protected(request: Request, email=Depends(check_cookie)):
return templates.TemplateResponse("logged_in.html", {"request": request})
#app.get("/logged_out", response_class=HTMLResponse)
async def logged_out(request: Request):
return templates.TemplateResponse("logged_out.html", {"request": request})
#app.get("/logout")
async def route_logout_and_remove_cookie():
response = RedirectResponse(url="/logged_out")
response.delete_cookie("Authorization", domain="127.0.0.1")
return response
if __name__ == '__main__':
uvicorn.run(app)
Related
I've implemented 'Sign In with Apple' from this source (https://gist.github.com/aamishbaloch/2f0e5d94055e1c29c0585d2f79a8634e?permalink_comment_id=3328115) taking into account the comments of NipunShaji and aj3sh. But it doesn't works because Apple sends incomplete data: I recieve
decoded = {'iss': 'https://appleid.apple.com', 'aud': '...', 'exp': 1664463442, 'iat': 1664377042, 'sub': '.....', 'at_hash': '....', 'auth_time': 1664377030, 'nonce_supported': True}
without email data).
According to the Apple's documentation typical response contains email: https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api/authenticating_users_with_sign_in_with_apple.
What I've missed?
Additional code:
view.py file:
class AppleSocialAuthView(GenericAPIView):
serializer_class = AppleSocialAuthSerializer
permission_classes = [AllowAny]
def post(self, request):
"""
POST with "auth_token"
Send an access token as from facebook to get user information
"""
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
data = (serializer.validated_data['auth_token'])
return Response(data, status=status.HTTP_200_OK)
serializer.py file:
class Apple(BaseOAuth2):
"""apple authentication backend"""
name = 'apple'
ACCESS_TOKEN_URL = 'https://appleid.apple.com/auth/token'
SCOPE_SEPARATOR = ','
ID_KEY = 'uid'
#handle_http_errors
def do_auth(self, access_token, *args, **kwargs):
"""
Finish the auth process once the access_token was retrieved
Get the email from ID token received from apple
"""
response_data = {}
client_id, client_secret = self.get_key_and_secret()
headers = {'content-type': "application/x-www-form-urlencoded"}
data = {
'client_id': client_id,
'client_secret': client_secret,
'code': access_token,
'grant_type': 'authorization_code',
'redirect_uri': settings.SOCIAL_AUTH_APPLE_REDIRECT_URL
}
res = requests.post(Apple.ACCESS_TOKEN_URL, data=data, headers=headers)
response_dict = res.json()
id_token = response_dict.get('id_token', None)
if id_token:
decoded = jwt.decode(id_token, '', options={"verify_signature": False}, verify=False)
print(decoded)
response_data.update({'email': decoded['email']}) if 'email' in decoded else None
response_data.update({'uid': decoded['sub']}) if 'sub' in decoded else None
response = kwargs.get('response') or {}
response.update(response_data)
response.update({'access_token': access_token}) if 'access_token' not in response else None
kwargs.update({'response': response, 'backend': self})
return self.strategy.authenticate(*args, **kwargs)
def get_user_details(self, response):
email = response.get('email', None)
details = {
'email': email,
}
return details
def get_key_and_secret(self):
headers = {
'kid': settings.SOCIAL_AUTH_APPLE_KEY_ID,
'alg': 'ES256',
}
payload = {
'iss': settings.SOCIAL_AUTH_APPLE_TEAM_ID,
'iat': int(time.time()),
'exp': int(time.time()) + 15552000,
'aud': 'https://appleid.apple.com',
'sub': settings.SOCIAL_AUTH_APPLE_CLIENT_ID,
}
client_secret = jwt.encode(
payload,
settings.SOCIAL_AUTH_APPLE_CLIENT_SECRET,
# algorithm='ES256',
headers=headers
)
return settings.SOCIAL_AUTH_APPLE_CLIENT_ID, client_secret
class AppleSocialAuthSerializer(serializers.Serializer):
auth_token = serializers.CharField()
def validate_auth_token(self, auth_token):
user_data = Apple()
user_data = user_data.do_auth(auth_token)
try:
email = user_data['email']
name = user_data['name']
provider = 'apple'
return register_social_user(
provider=provider, email=email, name=name)
except Exception as identifier:
raise serializers.ValidationError(
'The token is invalid or expired. Please login again.'
)
When I test this proces on my Mac (logging into web app), the end result is that I can see on my Mac, preferences -> Apple ID, that I'm using SSO for this application.
So it looks like Apple validated this Web App.
If they do send email, only first time the user is logging in to Web App, how Web App should know next time what user to log in?
There is no single parameter that would identify the user in decoded response (like some ID, which would also appear in their first response?
Best Regards, Marek
I'm trying to set up Superset with Auth0. I've found somewhat similar issues here and here.
I've set up the following configuration based on the first link above and trying to follow the Superset and Flask-AppBuilder docs:
from flask_appbuilder.security.manager import (
AUTH_OAUTH,
)
from superset.security import SupersetSecurityManager
import json
import logging
import string
import random
nonce = ''.join(random.choices(string.ascii_uppercase + string.digits + string.ascii_lowercase, k = 30))
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
AUTH_TYPE = AUTH_OAUTH
AUTH_USER_REGISTRATION = True
AUTH_USER_REGISTRATION_ROLE = "Admin"
AUTH0_URL = os.getenv('AUTH0_URL')
AUTH0_CLIENT_KEY = os.getenv('AUTH0_CLIENT_KEY')
AUTH0_CLIENT_SECRET = os.getenv('AUTH0_CLIENT_SECRET')
OAUTH_PROVIDERS = [
{ 'name':'auth0',
'token_key':'access_token',
'icon':'fa-at',
'remote_app': {
'api_base_url': AUTH0_URL,
'client_id': AUTH0_CLIENT_KEY,
'client_secret': AUTH0_CLIENT_SECRET,
'server_metadata_url': AUTH0_URL + '/.well-known/openid-configuration',
'client_kwargs': {
'scope': 'openid profile email'
},
'response_type': 'code token',
'nonce': nonce,
}
}
]
class CustomSsoSecurityManager(SupersetSecurityManager):
def oauth_user_info(self, provider, response=None):
logger.debug('oauth2 provider: {0}'.format(provider))
if provider == 'auth0':
res = self.appbuilder.sm.oauth_remotes[provider].get(AUTH0_URL + '/userinfo')
logger.debug('response: {0}'.format(res))
if res.raw.status != 200:
logger.error('Failed to obtain user info: %s', res.json())
return
# user_info = self.appbuilder.sm.oauth_remotes[provider].parse_id_token(res)
# logger.debug('user_info: {0}'.format(user_info))
me = res.json()
return {
'username' : me['email'],
'name' : me['name'],
'email' : me['email'],
}
CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager
The full error log message is:
2022-03-18 18:53:56,854:ERROR:flask_appbuilder.security.views:Error authorizing OAuth access token: 400 Bad Request: The browser (or proxy) sent a request that this server could not understand.
NOTES:
I can see an access_token parameter in the redirect url, so it seems to be working with Auth0 correctly.
I don't see any of the debug lines in the CustomSsoSecurityManager being written, so my guess is that I have not correctly set that up (or my logging is not correctly configured).
I've tried using both Regular Web Application and Single Page Application application types in Auth0, and both fail in the same way.
I would appreciate any help in understanding what I might be missing or what else I need to do to configure Auth0 to work with Superset.
I was able to make it work using the JSON Web Key Set endpoint provided by Auth0, look at this example and adapt it accordingly:
from jose import jwt
from requests import request
from superset.security import SupersetSecurityManager
class CustomSecurityManager(SupersetSecurityManager):
def request(self, url, method="GET", *args, **kwargs):
kwargs.setdefault("headers", {})
response = request(method, url, *args, **kwargs)
response.raise_for_status()
return response
def get_jwks(self, url, *args, **kwargs):
return self.request(url, *args, **kwargs).json()
def get_oauth_user_info(self, provider, response=None):
if provider == "auth0":
id_token = response["id_token"]
metadata = self.appbuilder.sm.oauth_remotes[provider].server_metadata
jwks = self.get_jwks(metadata["jwks_uri"])
audience = self.appbuilder.sm.oauth_remotes[provider].client_id
payload = jwt.decode(
id_token,
jwks,
algorithms=["RS256"],
audience=audience,
issuer=metadata["issuer"],
)
first_name, last_name = payload["name"].split(" ", 1)
return {
"email": payload["email"],
"username": payload["email"],
"first_name": first_name,
"last_name": last_name,
}
return super().get_oauth_user_info(provider, response)
I am trying to send a message to django consumers.py from one user to another after a user logs in. However, the chat_message function doesn't seem to be firing with the code I wrote in the log in function in views.py as "ccc" in not printing on the server side. New to Django Channels here. Would appreciate any input to this problem of mine.
consumers.py
class OnlineFriends(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = self.scope["user"]
print(self.room_name)
print(self.channel_layer)
self.room_group_name = 'chat_' + str(self.room_name)
print(self.room_group_name)
# Join room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
print("aa")
message = self.channel_name.split("-")
await self.send(text_data=json.dumps({
'message': message
}))
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': message
}
)
async def chat_message(self, event):
print("ccc")
message = event['message']
# Send message to WebSocket
await self.send(text_data=json.dumps({
'message': message
}))
users.views.py *Updated
class Login(View):
form_class = LoginForm
template = 'login.html'
def get(self, request):
form=self.form_class(None)
return render(request, self.template, {'form':form})
def post(self, request):
form = self.form_class(request.POST)
if form.is_valid():
username = form.cleaned_data.get("username")
password = form.cleaned_data.get("password")
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
user.onlinestatus = True
user.save()
for obj in user.friends.all():
print( user.friends.all())
if obj.onlinestatus == True:
print("obj: "+ str(obj.username))
channel_layer = get_channel_layer()
print("channel_layer: "+ str(channel_layer))
room_name = "chat_" + str(obj.username)
print("room_name: "+ str(room_name))
print(obj.channel_name)
async_to_sync(channel_layer.group_send(
room_name, {
"type": "chat.message",
"message": room_name,
}))
return redirect('home')
else:
return HttpResponse("No such user.")
else:
return render(request, 'login.html', {'form':form})
traceback updated
WebSocket HANDSHAKING /friendslist/ [127.0.0.1:49751] ****loading home page on browser****
foonghanyao ****output from consumers.OnlineFriends.connect****
RedisChannelLayer(hosts=[{'address': ('127.0.0.1', 6379)}])
chat_foonghanyao
WebSocket CONNECT /friendslist/ [127.0.0.1:49751]
HTTP GET /static/songs/images/favicon.png 200 [0.02, 127.0.0.1:61225]
<QuerySet [<User: foonghanyao>]> ****output from Login class in views.py****
obj: foonghanyao
channel_layer: RedisChannelLayer(hosts=[{'address': ('127.0.0.1', 6379)}])
room_name: chat_foonghanyao
specific.45d45ec67e0a4282b2066d67847e4bab!8ab6617c8be94d0dabefadf636ba886e
HTTP POST /accounts/login/ 302 [0.88, 127.0.0.1:60752]
<QuerySet [<Tag: apple>, <Tag: bee>]>
HTTP GET /home/ 200 [0.16, 127.0.0.1:60752]
WebSocket DISCONNECT /friendslist/ [127.0.0.1:55762] ****Disconnecting from websockets in login page****
aa
WebSocket HANDSHAKING /friendslist/ [127.0.0.1:58978] ****new page loading after being redirected following log in****
brandonfoong
RedisChannelLayer(hosts=[{'address': ('127.0.0.1', 6379)}])
chat_brandonfoong
WebSocket CONNECT /friendslist/ [127.0.0.1:58978]
sync.routing.py updated
websocket_urlpatterns = [
re_path(r"^friendslist/$", consumers.OnlineFriends.as_asgi(), name='friendslist'),
]
mainapp.asgi.py updated
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "music.settings")
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": AuthMiddlewareStack(
URLRouter(
sync.routing.websocket_urlpatterns,
)
),
})
I'm trying to send messages to specific websocket instances, but neither channel_layer.send, nor using channel_layer.group_send with unique groups for each instance seems to be working, no errors are being raised, they just aren't received by the instances.
The function that sends the message is:
def listRequest(auth_user, city):
request_country = city["country_name"]
request_city = city["city"]
request_location = request_city +", "+request_country
concatenate_service_email = auth_user.service + "-" + auth_user.email
this_request = LoginRequest(service_and_email=concatenate_service_email, location=request_location)
this_request.generate_challenge()
this_request.set_expiry(timezone.now() + timezone.timedelta(minutes=5))
this_request.save()
channel_layer = get_channel_layer()
print(auth_user.current_socket)
async_to_sync(channel_layer.group_send)(
auth_user.current_socket,{
"type": "new.request",
"service_and_email" : concatenate_service_email
},
)
My current working consumers.py (receive and scanrequest don't have anything that's likely to be relevant to the issue):
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
from .helpers import verify_identity, unique_group
from django.utils import timezone
from .models import Authentication, LoginRequest
import json
import time
class AuthConsumer(WebsocketConsumer):
account_list =[]
def connect(self):
print("Connect attempted")
print(self.channel_name)
print(unique_group(self.channel_name))
async_to_sync(self.channel_layer.group_add)(unique_group(self.channel_name), self.channel_name)
self.accept()
def disconnect(self, close_code):
print("Disconnect attempted")
async_to_sync(self.channel_layer.group_discard)(unique_group(self.channel_name), self.channel_name)
for i in self.account_list:
serviceEmailSplit = i.split("-")
try:
auth_user = Authentication.objects.get(service=serviceEmailSplit[0],email=serviceEmailSplit[1])
auth_user.set_socket("NONE")
auth_user.save()
except:
print("Error user %s does not exist" %i)
pass
def receive(self, text_data):
print("Receiving data")
if text_data[0:7] == "APPROVE":
data_as_list = text_data.split(",")
serviceEmailSplit = data_as_list[1].split("-")
auth_user = Authentication.objects.get(service=serviceEmailSplit[0],email=serviceEmailSplit[1])
this_request = LoginRequest.objects.get(service_and_email=data_as_list[1],approved=False, expiry__gt=timezone.now())
if verify_identity(auth_user.public_key, data_as_list[2], this_request.challenge):
this_request.set_approved()
self.send("Request Approved!")
else:
self.send("ERROR: User verification failed")
else:
self.account_list = text_data.split(",")
self.account_list.pop(-1)
print(self.account_list)
for i in self.account_list:
serviceEmailSplit = i.split("-")
try:
auth_user = Authentication.objects.get(service=serviceEmailSplit[0],email=serviceEmailSplit[1])
auth_user.set_socket(unique_group(self.channel_name))
auth_user.save()
except:
self.send("Error user %s does not exist" %i)
self.scanRequest()
def scanRequest(self):
requestSet = LoginRequest.objects.filter(service_and_email__in = self.account_list, approved = False, request_expiry__gt = timezone.now())
if requestSet.count() > 0:
for request in requestSet:
self.send(request.service_and_email+","+request.location+","+str(request.challenge))
else:
self.send("NOREQUESTS")
def new_request(self,event):
print("NEW REQUEST!")
this_request = LoginRequest.objects.filter(service_and_email = event["service_and_email"]).latest('request_expiry')
self.send(this_request.service_and_email+","+this_request.location+","+str(this_request.challenge))
And my routing.py:
from django.urls import re_path
from . import consumers
from django.conf.urls import url
websocket_urlpatterns = [
url(r"^ws/$", consumers.AuthConsumer.as_asgi()),
]
"NEW REQUEST!" is never printed, having tried to call it both by sending a message directly, and neither does using groups like I have written above.
My redis server appears to be working from testing like the documentation for the channels tutorial suggests:
https://channels.readthedocs.io/en/stable/tutorial/part_2.html
I'm pretty stumped after attempts to fix it, and I've looked at the other posts on stackoverflow with the same/similar issues and I'm already following whatever solutions they have in my code.
I want to return the user access token for oauth2 as soon as the user logs in with a login api.
Till now I have created a login and register api and I am able to genereate access token via /o/token but I want it as a return value.
Here is my views.py :-
"""
POST auth/login/
"""
# This permission class will overide the global permission
# class setting
permission_classes = (AllowAny,)
serializer_class = UserSerializer
queryset = User.objects.all()
def post(self, request, *args, **kwargs):
username = request.data.get("username", "")
password = request.data.get("password", "")
user = authenticate(request, username=username, password=password)
if user is not None:
# login saves the user’s ID in the session,
# using Django’s session framework.
login(request, user)
return redirect('list-user')
return Response(status=status.HTTP_401_UNAUTHORIZED)
class RegisterUserView(generics.CreateAPIView):
"""
POST auth/register/
"""
permission_classes = (AllowAny,)
serializer_class = UserRegistrationSerializer
def post(self, request, *args, **kwargs):
username = request.data.get("username", "")
password = request.data.get("password", "")
email = request.data.get("email", "")
if not username and not password and not email:
return Response(
data={
"message": "username, password and email is required to register a user"
},
status=status.HTTP_400_BAD_REQUEST
)
new_user = User.objects.create_user(
username=username, password=password, email=email
)
return Response(status=status.HTTP_201_CREATED)
and here is my serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'password']
class UserRegistrationSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'email','password']
urls.py
path('admin/', admin.site.urls),
path('', include('users.urls')),
path('o/', include('oauth2_provider.urls', namespace='oauth2_provider')),
]
urls.py for users app
urlpatterns = [
path('users/', views.UserListView.as_view(), name='list-user'),
path('auth/login/', views.LoginView.as_view(), name="auth-login"),
path('auth/register/', views.RegisterUserView.as_view(), name="auth-register")
]
How can I implement it?
You will have to get token from oauth2_provider view for auth token generation and and then you can modify response according to your requirement.
from oauth2_provider.views.base import TokenView
class CustomAuthView(generics.CreateAPIView):
permission_classes = (AllowAny,)
serializer_class = UserSerializer
def post(self, request, *args, **kwargs):
oauth_response = TokenView.as_view(request, *args, **kwargs)
if oauth_response.status == 200:
data = oauth_response.data
# update data according to your requirement
return response.Response(data)
else:
return oauth_response
If you want to just change url for auth view, then you can do it by adding a new url which will point towards TokenView like this
from oauth2_provider.views.base import TokenView
path('auth/login/', TokenView.as_view(), name="auth-login"),
You will have to provide following parameters to your api
grant_type
username
password
client_id
client_secret