Fast API, test endpoints by Postman - python

I want to test some endpoints, in particular sign-in endpoint by using Postman. But every time I send request I get en error whether 422 or 401 (the login and password are correct).
Is it pissible to test sign-in with Postman?
router = APIRouter(
prefix='/auth'
)
#router.post('/sign-in', response_model=Token)
def sign_in(form_data: OAuth2PasswordRequestForm = Depends(), service: AuthService = Depends()):
return service.authenticate_user(form_data.username, form_data.password)

Related

How do you keep authorization active when entering the docs in Fastapi?

I am using some basic oauth2 authentication in Python, fast api. I want to hide all of the swagger docs behind a login, and ideally I would want all the login logic to be handled by the oauth2scheme, such that when a user logs in to see the docs, they do not need to reauthenticate in order to test any of the endpoints.
The closest I have gotten to a solution is logging in the user by saving a cookie to the request, then overwriting the swagger ui to check the cookie and logout the user. The only functionality I am missing is having the user be automatically authenticated once entering the docs based on this cookie. Relevant code below:
#dbparse.get("/", include_in_schema=False, response_class=HTMLResponse)
async def login_page(request: Request):
return templates.TemplateResponse("login.html", {"request": request})
#dbparse.post("/login", include_in_schema=False)
async def perform_login(
request: Request,
response: Response,
username: Optional[str] = Form(None),
password: Optional[str] = Form(None),
):
try:
if not (username and password):
return RedirectResponse(
"/?error=true", status_code=status.HTTP_303_SEE_OTHER
)
# Fake an oauth login form to route login attempts to previously written code
form = OAuth2PasswordRequestForm(username=username, password=password, scope="")
token = await generate_token(form)
response = RedirectResponse("/docs", status_code=status.HTTP_302_FOUND)
# Set the user's login cookie so that we can have them auth'd later
response.set_cookie(
key="Authorization",
value=f"{token['token_type'].capitalize()} {token['access_token']}",
)
return response
except:
return RedirectResponse("/?error=true", status_code=status.HTTP_303_SEE_OTHER)
#dbparse.get("/docs", include_in_schema=False)
async def custom_swagger_ui_html(request: Request):
if request.cookies:
try:
token = await generate_token(None, request.cookies)
except:
response = RedirectResponse("/?expired=true", status_code=status.HTTP_303_SEE_OTHER)
response.delete_cookie(key="Authorization")
return response
return get_swagger_ui_html(...)
#dbparse.post("/token", include_in_schema=False)
async def generate_token(form_data: OAuth2PasswordRequestForm = Depends(), cached_token = None):
print("==========================\nGENERATE_TOKEN()\n==========================")
if cached_token:
try:
_, access_token = cached_token["Authorization"].split(" ")
_ = decode_token(access_token)
# Ensure that the token in cookies is working. (Confirmed to work as intended)
return {"access_token": access_token, "token_type": "bearer"}
except:
raise
user = authenticate(form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid username/password",
)
token = create_token(user)
return {"access_token": token, "token_type": "bearer"}
If there is an easier solution, I was unable to find it. The key functionality I am looking for is to use a cookie to authenticate the swagger docs for endpoint use. The endpoints need to require authentication, but ideally when you log in before seeing the docs, this authentication should carry over.

Authlib Securing Login Endpoint and Passing Identity to Authorized Endpoint

I am trying to use Python's Authlib package. For that I am defining a login endpoint and an authorized redirect endpoint. I am trying to secure the login endpoint, verify identity and pass it to the authorized endpoint so that tokens can be saved with user's identity.
class GithubLogin(Resource):
#classmethod
def get(cls):
response = custom_verify_jwt_in_request(request)
if response.status_code != 200:
return {"message": "Unauthorized"}, 401
redirect_uri = url_for("github.authorize", _external=True, _scheme=os.getenv("URL_SCHEME", "https"))
return oauth.github.authorize_redirect(redirect_uri, email=response['email'])
class GithubAuthorize(Resource):
#classmethod
def get(cls):
print(request.args)
# Here I can't find the email parameter I have supplied before the redirect.
Is there a way to handle this case? Preferably without having a dynamic redirect url because apparently it has security flaws.
Thanks!

Using JWT between two Flask applications

I have two separate Flask applications, one is an API with the domain "admin.company.com" and the second one is a dashboard under the domain "dashboard.company.com".
My main goal is to secure the api and enable authentication.
I set up authentication on the api and when I'm testing it via the swagger-ui it works good with no issues. I manage to authenticate myself and submit requests. On top of that, in the token_required() below I coded a section that expects to receive JWT and decode it:
def token_required(f):
#wraps(f)
def decorator(*args, **kwargs):
token = None
if 'jwt-token' in request.headers:
token = request.headers['jwt-token']
if not token:
return jsonify({'message': 'a valid token is missing'})
try:
current_user = False
# for authentication via the swagger-ui
if token == 'my_awesome_password':
current_user = 'admin'
else:
data = jwt.decode(token, app.secret_key)
current_user = 'admin' if data['public_id'] == 'admin' else False
if not current_user:
return jsonify({'message': 'token is invalid'})
except:
return jsonify({'message': 'token is invalid'})
return f(*args, **kwargs)
return decorator
The problem is with the dashboard application:
On the dashboard app, I configured the /login route to generate JWT (which needs to be sent to the api app in every HTTP request I made), then do a set_cookie() with httpOnly=True flag (for security purposes), but then how can the JavaScript access it when it has to make XHR requests? Having the httpOnly flag on makes it unreachable.
If using httpOnly=True is not the ideal way in this case, how can I tackle it and make sure the JWT will always be sent to the api app in the request headers?
I'm using PyJWT package.
Thanks for any help.
If you use JWTs for XHR requests, then you don't necessarily need to use cookies – the browser/consumer could store the token and attach it to every request. If you do want to use cookies (this is also a viable option) then you don't need to worry about attaching the JWT as the browser will do that automatically for the domains you specified in the set_cookie command.

Flask Restful with PyJWT Authentication

Background:
I'm trying to prototype a quick token-based authentication using Flask-Restful and PyJWT. The idea is that I will have a form with email and password and when user clicks submit, it will generate a token and save it in client side browser and use it in any subsequent requests until the token expires.
Trouble
In my prototype, I was able to create a token using JWT but I don't have an idea of how to pass the JWT into a subsequent request. When I do it in the Postman, it works because I can specify the Authorization header with token in there. But when I login in through UI and token is generated, I do not know how to pass the token generated into a subsequent request (/protected) by making the token persists in the header until it expires. Currently when I login from UI and go to /protected, the Authorization header is missing in /protected header.
Code
class LoginAPI(Resource):
# added as /login
def get(self):
"""
renders a simple HTML with email and password in a form.
"""
headers = {'Content-Type': 'text/html'}
return make_response(render_template('login.html'), 200, headers)
def post(self):
email = request.form.get('email')
password = request.form.get('password')
# assuming the validation has passed.
payload = {
'user_id': query_user.id,
'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=10)
}
token = jwt\
.encode(payload, current_app.config['JWT_SECRET'], current_app.config['JWT_ALGORITHM'])\
.decode('utf-8')
# Is below the right way to set the token into header to be used in subsequent request?
# request.headers.authorization = token
# when {'authorization': token} below as a header, the header only shows up for /login not for any subsequent request.
return make_response({'result': 'success', 'token': token}, 200, {'authorization': token} )
class ProtectedAPI(Resource):
#check_auth
def get(self):
return jsonify({'result': 'success', 'message': 'this is a protected view.'})
# decorator to check auth and give access to /protected
def check_auth(f):
#wraps(f)
def authentication(*args, **kws):
# always get a None here.
jwt_token = request.headers.get('authorization', None)
payload = jwt.decode(jwt_token, 'secret_key', algorithms='HS512'])
# other validation below skipped.
return f(*args, **kws)
return authentication
To persist the access_token you have to store on the client and pass it into your header every time you call your backend and check the authenticity of the token every time.

How to login user through all routes with flask?

I'm stuck in a loop, I have a web app, the login and signup was made using Flask on the Backend and Jinja for templating, no JS was used for these two routes, I used WTForms for form validation and Flask Login to handle login .
Now, when user login he is redirected to a dashboard, the dashboard is a single page application that use React, I send HTTP request to my API which is built in Flask (same app, but different blueprints).
The problem is that these APIs routes are avalable only for authenticated users, so I used a token system to authenticate API calls from the client to the server .
To do so, I created a request loader for my login manager, that using a token parameter in the query would decode it , get the email and password and return the respective User, otherwise if it fail it returns None
#login_manager.request_loader
def load_user(request):
if "token" in request.args:
decoded_token = base64.standard_b64decode(request.args["token"])
email = decoded_token.split(b':',1)[0]
password = decoded_token.split(b':',1)[1]
possible_user = User.select().where(User.email == email)[0]
if possible_user.password.encode() == password:
return possible_user
else:
return None
else:
return None
return None
This token is sent to the user when he login and get redirected to the dashboard :
User login successfULY
Get redirected to dashboard
Dashboard make an ajax call to "/user-data" , which is protected, this route should use current_user to encode the token and send it back to dashboard single page app to use it in further API calls .
The problem:
When I request the "/user-data" through AJAX from dashboard, current_user is empty thus, the call return a 401 unauthorized request even though the user did login, and in the login route, when I print current_user I get the current user logged in as expected . So my question is how can I keep some way to exhcange credentials between login and "user-data" route ? I tried storing the data in a session in the login route then re-use it in the '/user-data' but the session becomes empty whenever 'user-data' is called .
Here's 'user-data' route :
#auth_bp.route("/user-data")
#login_required
def user_data():
# Return Base 64 encode username:password to use in API calls!
print("Current user")
print(current_user)
print(session)
code = base64.standard_b64encode(current_user.email.encode() + b':' + current_user.password.encode())
print(base64.standard_b64decode(code))
return code

Categories

Resources