Using flask_login and flask-JWT together in a REST API - flask-restful

I am new to flask, recently learned about flask_security/flask_login/flask_user.
I wish that somehow I could use flask_login along with flask-JWT, for the REST API.
Basically, I'd like to have the features like remember-me, forgot-password etc, from the flask_login
Upon searching, I found that it couldn't be done on the same flask view.
Could somebody guide me, how to do it?
Thanks.

flask-login provides the request_loader callback exactly for this purpose, for authenticating requests in a custom way.
In my case, I added this to my create_app function:
#login_manager.request_loader
def load_user_from_request(request):
auth_headers = request.headers.get('Authorization', '').split()
if len(auth_headers) != 2:
return None
try:
token = auth_headers[1]
data = jwt.decode(token, current_app.config['SECRET_KEY'])
user = User.by_email(data['sub'])
if user:
return user
except jwt.ExpiredSignatureError:
return None
except (jwt.InvalidTokenError, Exception) as e:
return None
return None
Otherwise, I followed this tutorial, so the token is created like this (in the login function):
token = jwt.encode({
'sub': user.email,
'iat':datetime.utcnow(),
'exp': datetime.utcnow() + timedelta(minutes=30)},
current_app.config['SECRET_KEY'])
This way you can just use #login_required from flask-login instead of defining a custom decorator to protect views.
I used PyJWT instead of Flask-JWT since it seems Flask-JWT is discontinued.

Related

Handle Firebase client-side token and access to protected pages

I'm using Firebase auth to login with Facebook, Google and email/pass. Basically, everything runs client-side, I make a call to Firebase and I receive an object containing an access token (that is a JWT ID Token), a customer id and its email. When I get this object, I put it into a persistent store (local storage, I know it's bad) and I perform an API call to one of my sveltekit endpoint that will in turn make another API call to a backend API (written in Go) to get all the user informations: firstname, lastname, phone, address, stats etc. To give a little bit of context, below is a diagram to illustrate what's happening when a user sign-in using Facebook.
Up to now, I just put the Firebase object into a store and just check if the information are there to allow access to a particular page. This check is done in the +layout.svelte page of the directory containing the page to be protected. It looks like something like this:
onMount(() => {
// redirect if not authenticated
if (browser && !$authStore?.uid) goto(`/auth/sign-in`);
});
It's probably not a good thing especially since my store persists in the local storage and therefore is prone to some javascript manipulation.
From my understanding, there's at least 2 things that could be better implemented but I may be wrong:
Set the access token in an httponly cookie straight after receiving it from Firebase. This would avoid storing the access token (that is a JWT) in the local storage and It could be used to authorize access or not to some protected pages...
I receive the Firebase authentication object on client-side buthttp only cookie can only be created from server side. I thought about sending the object as a payload to a POST sveltekit endpoint (like /routes/api/auth/token/+server.js) and set the cookie from here but apparently cookies is not available in a sveltekit endpoint.
So, how and where should I set this cookie ? I see that cookies is available in the load function of a +layout.server.js file, as well as in the handle function of a hooks.server.js file, but I don't see the logic here.
Populate locals.userwith the authenticated user once I've performed a call to my backend. Well, here, it's not obvious to me because I think point 1) would be enough to manage access to protected pages, but I see that a check of locals.user is something I've seen elsewhere.
I tried to set locals.user in the sveltekit endpoint that is making the API call to the backend API:
// /routes/api/users/[uid]/+server.js
import { json } from "#sveltejs/kit";
import axios from "axios";
import { GO_API_GATEWAY_BASE_URL } from "$env/static/private";
export async function GET({ request, locals, params }) {
try {
const config = {
method: "get",
baseURL: GO_API_GATEWAY_BASE_URL,
url: `/users/${params.uid}`,
headers: {
Authorization: `Bearer ${uidToken}`, // <-- the Firebase ID Token
},
withCredentials: true,
};
const res = await axios(config);
// set locals
locals.user = json(res.data); // <--- DOESN'T SEEM TO WORK
return json(res.data);
} catch (error) {...}
}
...but in the +layout.server.js page that I've created I see nothing:
// routes/app/protected_pages/+layout.server.js
import { redirect } from "#sveltejs/kit";
export function load({ locals }) {
console.log(locals); // <----- contains an empty object: {}
if (!locals.user) throw redirect(302, "/auth/sign-in");
}
Thank you so much for your help

Basic token authorization in FastApi

I need help understanding how to process a user-supplied token in my FastApi app.
I have a simple app that takes a user-session key, this may be a jwt or not. I will then call a separate API to validate this token and proceed with the request or not.
Where should this key go in the request:
In the Authorization header as a basic token?
In a custom user-session header key/value?
In the request body with the rest of the required information?
I've been playing around with option 2 and have found several ways of doing it:
Using APIKey as described here:
async def create(api_key: APIKey = Depends(validate)):
Declaring it in the function as described in the docs here
async def create(user_session: str = Header(description="The Users session key")): and having a separate Depends in the router config,
The best approach is to build a custom dependency using any one of the already existing authentication dependencies as a reference.
Example:
class APIKeyHeader(APIKeyBase):
def __init__(
self,
*,
name: str,
scheme_name: Optional[str] = None,
description: Optional[str] = None,
auto_error: bool = True
):
self.model: APIKey = APIKey(
**{"in": APIKeyIn.header}, name=name, description=description
)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
async def __call__(self, request: Request) -> Optional[str]:
api_key: str = request.headers.get(self.model.name)
# add your logic here, something like the one below
if not api_key:
if self.auto_error:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
else:
return None
return api_key
After that, just follow this from documentation to use your dependency.

How to verify the request is coming from my application clientside?

I have an app where users can create posts. There is no login or user account needed! They submit content with a form as post request. The post request refers to my api endpoint. I also have some other api points which are fetching data.
My goal is to protect the api endpoints completely except some specific sites who are allowed to request the api ( I want to accomplish this by having domain name and a secure string in my database which will be asked for if its valid or not if you call the api). This seems good for me. But I also need to make sure that my own application is still able to call the api endpoints. And there is my big problem. I have no idea how to implement this and I didn't find anything good.
So the api endpoints should only be accessible for:
Next.js Application itself if somebody does the posting for example
some other selected domains which are getting credentials which are saved in my database.
Hopefully somebody has an idea.
I thought to maybe accomplish it by using env vars, read them in getinitalprops and reuse it in my post request (on the client side it can't be read) and on my api endpoint its readable again. Sadly it doesn't work as expected so I hope you have a smart idea/code example how to get this working without using any account/login strategy because in my case its not needed.
index.js
import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
export default function Home(props) {
async function post() {
console.log(process.env.MYSECRET)
const response = await fetch('/api/hello', {
method: 'POST',
body: JSON.stringify(process.env.MYSECRET),
})
if (!response.ok) {
console.log(response.statusText)
}
console.log(JSON.stringify(response))
return await response.json().then(s => {
console.log(s)
})
}
return (
<div className={styles.container}>
<button onClick={post}>Press me</button>
</div>
)
}
export async function getStaticProps(context) {
const myvar = process.env.MYSECRET
return {
props: { myvar },
}
}
api
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
export default function handler(req, res) {
const mysecret = req.body
res.status(200).json({ name: mysecret })
}
From what I understand, you want to create an API without user authentication and protect it from requests that are not coming from your client application.
First of all, I prefer to warn you, unless you only authorize requests coming from certain IPs (be careful with IP Spoofing methods which could bypass this protection), this will not be possible. If you set up an API key that is shared by all clients, reverse engineering or sniffing HTTP requests will retrieve that key and impersonate your application.
To my knowledge, there is no way to counter this apart from setting up a user authentication system.

Calling a REST API using Azure function App and store data in Azure container

I have a requirement to call a rest api and store the resulting json in azure storage container. I have tried standalone python coding to extract the data from rest api and able to successfully receive the data from api that has pagination. Now I need to integrate/modify this python coding inside Azure Function and will ultimately store the resulting json data in a azure storage container. I am fairly new to Azure and hence need your guidance on how to tweak this code to suit in Azure function that will in turn push the json to azure container finally.
response = requests.post(base_url,
auth=(client_id, client_secret), data={'grant_type':grant_type,'client_id':client_id,'client_secret':client_secret,'resource':resource})
acc_token_json = response.json()
access_token = json.loads(response.text)
token = access_token['access_token']
#call API to know total pages
API_Key = 'xxxxx'
api_url='https://api.example.com?pageSize=10&page=1&sortBy=orderid&sortDirection=asc'
headers = {
'Authorization': token,
'API-Key': API_Key,
}
r = requests.get(url=api_url, headers=headers).json()
total_record=int(r['pagination']['total'])
total_page=round(total_record/500)+1
#loop through all pages
all_items = []
for page in range(0, total_page):
url = "https://api.example.com?pageSize=500&sortBy=orderid&sortDirection=asc&page="+str(page)
response = requests.get(url=url, headers=headers).json()
response_data=response['data']
all_items.append(response_data)
Your inputs/guidances are very much appreciated.
You can put the logic in the body of the function.(Function is just set the condition of trigger.)
For example, if you are based on HttpTrigger:
import logging
import azure.functions as func
def main(req: func.HttpRequest) -> func.HttpResponse:
logging.info('Python HTTP trigger function processed a request.')
'''
#Put the your logic code here.
'''
return func.HttpResponse(
"This is a test.",
status_code=200
)
And you can also use blob output to achieve your requirement, it is easier, have a look of this offcial doc:
https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-blob-output?tabs=python#example
Let me know if have any problem.

Endpoint belongs to different authority

trying to use Azure AD as OpenID provider with IdentityModel package
However the problem is that it produces wrong endpoint configuration
var client = new HttpClient();
const string identityUrl = "https://login.microsoftonline.com/00edae13-e792-4bc1-92ef-92a02ec1d939/v2.0";
const string restUrl = "https://localhost:44321";
var disco = await client.GetDiscoveryDocumentAsync(identityUrl);
if (disco.IsError)
{
Console.WriteLine(disco.Error);
return;
}
returns error
Endpoint belongs to different authority:
https://login.microsoftonline.com/00edae13-e792-4bc1-92ef-92a02ec1d939/oauth2/v2.0/authorize
openid-configuration output is
{"authorization_endpoint":"https://login.microsoftonline.com/00edae13-e792-4bc1-92ef-92a02ec1d939/oauth2/v2.0/authorize",
"token_endpoint":"https://login.microsoftonline.com/00edae13-e792-4bc1-92ef-92a02ec1d939/oauth2/v2.0/token" ... }
oauth2 is added between the tenatID and version. I suppose this is why openid metadata validation fails.
Is it possible to configure AzureAD to return correct metadata for the openid-configuration ?
Regards
could you find a solution for this? The only way I could figure out (far to be the optimal solution) is to add the endpoints to a list of additional endpoint base addresses. Otherwise you have to set the validations to false as stated in the comments above.
var client = httpClientFactory.CreateClient();
var disco = await client.GetDiscoveryDocumentAsync(
new DiscoveryDocumentRequest
{
Address = "https://login.microsoftonline.com/00edae13-e792-4bc1-92ef-92a02ec1d939/v2.0",
Policy =
{
ValidateIssuerName = true,
ValidateEndpoints = true,
AdditionalEndpointBaseAddresses = { "https://login.microsoftonline.com/00edae13-e792-4bc1-92ef-92a02ec1d939/oauth2/v2.0/token",
"https://login.microsoftonline.com/00edae13-e792-4bc1-92ef-92a02ec1d939/oauth2/v2.0/authorize",
"https://login.microsoftonline.com/00edae13-e792-4bc1-92ef-92a02ec1d939/discovery/v2.0/keys",
"https://login.microsoftonline.com/00edae13-e792-4bc1-92ef-92a02ec1d939/oauth2/v2.0/devicecode",
"https://graph.microsoft.com/oidc/userinfo",
"https://login.microsoftonline.com/00edae13-e792-4bc1-92ef-92a02ec1d939/oauth2/v2.0/logout"
}
},
}
);
If you take a look at the code inside IdentityModel repository, you can see that the default validation of the endpoints validates them by doing a "starts with" method. https://github.com/IdentityModel/IdentityModel/blob/1db21e2677de6896bc11227c70b927c502e20898/src/Client/StringComparisonAuthorityValidationStrategy.cs#L46
Then the only two required AdditionalEndpointBaseAddresses inside the DiscoveryDocumentRequest Policy field you need to add are "https://login.microsoftonline.com/<guid>" and "https://graph.microsoft.com/oidc/userinfo".
I had the same problem as well and when i upgraded IdentityModel to version 2.16.1 the problem was solved
Azure AD seems to need Additional Endpoints configuration as #flacid-snake suggested. Setting validate endpoints to False is a security threat and should be avoided.
The best way is to make it configurable, preferable in the UI when you configure the SSO server. Endpoints can change and they should be easy to change. It will also make it easier if you later decide to support Okta or other providers and they require additional endpoints.
As of June 2021 you also need to include Kerberos endpoint like:
https://login.microsoftonline.com/888861fc-dd99-4521-a00f-ad8888e9ecc8bfgh/kerberos (replace with your directory tenant id).

Resources