Flask JWT extend validity of token on each request - python

Scenario
A logged in user will have a token expiry of 24 hours. Within that period, all request with #jwt_required decorator will have the current access token's expiry extended by another 24 hours. There is a maximum validity of 168(24 * 7) hours.
It is possible to use access_token and refresh_token.
ret = {
'access_token': create_access_token(identity=username, fresh=True),
'refresh_token': create_refresh_token(identity=username)
}
But that means every API call from my applicatino will be two requests:
1. Actual HTTP Request
2. Refresh the auth token
#app.route('/refresh', methods=['POST'])
#jwt_refresh_token_required
def refresh():
current_user = get_jwt_identity()
ret = {
'access_token': create_access_token(identity=current_user)
}
return jsonify(ret), 200
Is there a way to implicitly extend an auth token?

EDIT: There is now documentation around this here: https://flask-jwt-extended.readthedocs.io/en/latest/refreshing_tokens/
Author of flask-jwt-extended here. Technically you cannot actually extend a token, you can only replace it with a new JWT that has a new expires time. There are a few ways you could simulate this though.
First, instead of having the client request a new token, you could have the server itself just implicitly send back a new token on every request. You could send the new JWTs back in a header instead of in the JSON payload, so that you wouldn't have to modify you JSON data to account for the possibility of a new JWT. Your clients would need to be aware of this though, they would need to check for that new header on every request and replace their current JWT with the new one if it is present. You could probably use a flask after_request method to do this, so you didn't have to add that functionality to all your endpoints. A similar effect could be achieved when storing the JWTs in cookies, with the differences being that cookies are automatically stored in your browser (so your client wouldn't have to manually look for them on every request), and with the added complexity of CSRF protection if you go this route (http://flask-jwt-extended.readthedocs.io/en/latest/tokens_in_cookies.html).
The above should work fine, but you will be creating a lot of access tokens that are thrown away right after being created, which probably isn't ideal. A variation of the above is to check if the token is near expiring (maybe if it is more then half way to being expired) and only create and return a new token if that is the case. Another variation of this would be to have the client check if the token is about to expire (via javascript) and if it is, use the refresh token to request a new access token. To do that, you would need to split the JWT on dots ('.'), base64 decode the second set of strings from that split (index 1), and grab the 'exp' data from there.
A second way you could do this is actually wait for a token to expire, and then use the refresh token to generate a new access token and remake the request (reactive instead of proactive). That might look like making a request, checking if the http code is 401, if so use the refresh token to generate a new access token, then making the request again.
Hope this helps :)

app = Flask(__name__)
app.config["JWT_SECRET_KEY"] = "super-secret" # Change this!
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(hours=1)
app.config["JWT_REFRESH_TOKEN_EXPIRES"] = timedelta(days=30)
jwt = JWTManager(app)
change time according to your requirement

Related

FastAPI's RedirectResponse doesn't work as expected in Swagger UI

I have a FastAPI app with a download endpoint. What this download endpoint does is to use a BlobServiceClient (for Azure Blob Storage) to generate a token and a Blob URL to a file specified in the request. What I want to do is to redirect the user to that URL. Here is a code snippet of the download enpoint (I commented some things out because I'm not allowed to show the code).
#router.get("..path", tags=["some tags"], summary=..., responses={404: {"model": ...}, 403: {"model": ...}, 307: {"model": ...}}, response_model_exclude_none=True)
async def download_file(
# there's a depends on an API key
blob_path: str = Query(
...
)):
credential = ClientSecretCredential(...) //secrets
blob_service_client = BlobServiceClient(f"https://{storage_account}.blob.core.windows.net", credential=credential)
user_delegation_key = blob_service_client.get_user_delegation_key(key_start_time=datetime.utcnow(),key_expiry_time=datetime.utcnow() + timedelta(minutes=30))
token = generate_blob_sas(account_name=...,
container_name=...,
blob_name=blob_path,
user_delegation_key=user_delegation_key,
permission=BlobSasPermissions(read=True),
expiry=datetime.utcnow() + timedelta(minutes=30))
blob_url = f'https://{storage_account}.blob.core.windows.net/{container_name}/{blob_path}?{token}'
print(blob_url)
response = RedirectResponse(blob_url)
return response
What I expected is the query to be executed, and after the response is returned, the download to start in the background or in a separate tab. What I've got instead is a different response as you can see in the Swagger:
I also had a look in the Network tab to see what is happening with that request:
Looks like there is an OPTIONS request and I assume that I'm getting the response to that request. Not sure if this is how Swagger handles the request. Any idea how/why this is happening and how to fix it? Thank you!
To start with, the HTTP OPTIONS, in CORS, is a preflight request that is automatically issued by the browser, before the actual request—is not the one that returns the File response. It requests the permitted communication options for a given server, and the server responds with an Access-Control-Allow-Methods header including a set of permitted methods (e.g., Access-Control-Allow-Methods: OPTIONS, GET, HEAD, POST, DELETE). The preflight response can be optionally cached for the requests created in the same URL using Access-Control-Max-Age header, thus allowing the server to limit the number of preflight requests. The value of this header is expressed in seconds; hence, allowing caching for 10 minutes, for example, would look as Access-Control-Max-Age: 600.
As for the RedirectResponse, Swagger UI always follows redirect responses. In a fetch request, for instance, the redirect parameter would be set to follow, indicating that the redirect should be followed. This means that Swagger UI follows the redirect and waits for the response to be completely received before providing you with a Download file link (as shown in the screenshot you provided above) that would allow you to download the file. That is also why you can't see the download starting either in the background or in a new tab. As mentioned in the linked github post above, it is not possible to change that behaviour, which could allow you to handle it differently, similar to the approach demonstrated in this answer.
Instead of using Swagger UI to test that specific endpoint, you can either test it directly through typing the URL to your API endpoint in the address bar of your browser (since it is a GET endpoint, you can do that, as when you type a URL in the address bar of your browser, it performs a GET request), or create your own custom Template (or use HTMLResponse) and submit an HTML <form>, as shown in this answer.

Automatically getting Mojang bearer token

I am writing a program that can change your Mojang username at whatever time you specify (i.e. the name "tom" becomes available at 09:05:39 so you would set the program to change your name to "tom" at that time). To do this you need a bearer token. I can log into minecraft.net and use EditThisCookie to access that, but I want to be able to have the program wait until it is within a minute of the name dropping to gather proxies and the bearer token automatically for situations where a name is becoming available in the middle of the night or I'm just lazy. I'm not sure why my function for getting the bearer token won't work. I referenced this to write this code.
def getAT():
jsonForAT = json.dumps({"agent":{"name":"Minecraft","version":1},"username":email,"password":password,"clientToken":""})
headersForAT = {'Content-Type': 'application/json'}
requestForAT = requests.post('https://authserver.mojang.com/authenticate', data=jsonForAT, headers=headersForAT)
pullATRequestData = requestForAT.json()
AT = pullATRequestData["accessToken"]
return AT
I am really confused since when I use this, I get a 401 error but the program works when I get the token manually.
I also was experiencing the same issue. Then, I realized that Minecraft/Mojang accounts have security questions. If your function is getting the bearer token and your issue is that your access is denied when posting a name change request:
1st make sure your formatting it properly:
headers={'Authorization': f"Bearer {TOKEN}"
Notice the space between "Bearer" and {TOKEN}
2nd: For some reason, Mojang needs you to send a Get request to their security challenges endpoint. You don't have to do anything with the returned data..
When you got the token manually, I'm assuming your browser automatically sent this get request and that's why it worked. If you're using proxies, you'll probably need to send a post request with your security question answers, look at the API link for documentation.
For Example:
res = requests.get(
"https://api.mojang.com/user/security/challenges",
headers={"Authorization": f"Bearer {TOKEN}"},
)
https://wiki.vg/Mojang_API
Go to the bottom under "Security question-answer flow"

How to get oauth2-token for Google Analytics Reporting API (REST method) in Python

I want to route my Google Analytics Reporting API request (code will be in AWS Lambda) through a gateway which accepts a REST endpoint only. Since I cant use the Client package method in my interaction with the gateway, I need to query the API as a REST-ful endpoint.
The official document says this (Link) :
Authorization: Bearer {oauth2-token}
GET https://www.googleapis.com/analytics/v3/data/ga
?ids=ga:12345
&start-date=2008-10-01
&end-date=2008-10-31
&metrics=ga:sessions,ga:bounces
I do not know to create the oauth2-token in Python. I have created a service account and have the secrets_json which includes the client id and secret key.
Then client package method as given in this link works. But I need the Rest method only!
Using these, how can I create the oauth2-token ?
You can use Oauth2 for this I have done it in the past but you will need to monitor it. You will need to authorize this code once and save the refresh token. Refresh tokens are long lived they normally dont expire but your code should be able to contact you if it does so that you can authorize it again. If you save the refresh token you can use the last step at any time to request a new access token.
Oauth2 is basicly built up into three calls. I can give you the HTTP calls i will let you work out the Python Google 3 Legged OAuth2 Flow
Authencation and authorization
The first thing you need is the permission of the user. To get that you build a link on the authorization server. This is a HTTP get request you can place it in a normal browser window to test it.
GET https://accounts.google.com/o/oauth2/auth?client_id={clientid}&redirect_uri=urn:ietf:wg:oauth:2.0:oob&scope=https://www.googleapis.com/auth/analytics.readonly&response_type=code
Note on redirect uri. If you are running this on a server or something then use urn:ietf:wg:oauth:2.0:oob it basicly tells the server to return the code back where it came from other wise if you are hosing on a website you can supply a url to the page that will be handling the response.
If the user accepts the above then you will have an authorization code.
Exchange code
What you need to do next is exchange the authorization code returned by the above response and request an access token and a refresh token. THis is a http post call
POST https://accounts.google.com/o/oauth2/token
code=4/X9lG6uWd8-MMJPElWggHZRzyFKtp.QubAT_P-GEwePvB8fYmgkJzntDnaiAI&client_id={ClientId}&client_secret={ClientSecret}&redirect_uri=urn:ietf:wg:oauth:2.0:oob&grant_type=authorization_code
The body parameter should be as i have shown separated by & and the content type of the request is application/x-www-form-urlencoded
Responce
{
"access_token" : "ya29.1.AADtN_VSBMC2Ga2lhxsTKjVQ_ROco8VbD6h01aj4PcKHLm6qvHbNtn-_BIzXMw",
"token_type" : "Bearer",
"expires_in" : 3600,
"refresh_token" : "1/J-3zPA8XR1o_cXebV9sDKn_f5MTqaFhKFxH-3PUPiJ4"
}
The access token can be used in all of your requests to the api by adding either an authorization header bearer token with the access token or by sending access_token= as your parameter in your requests.
Refresh access token
Refresh tokens are long lived they should not expire they can so you code should be able to handle that but normally they are good forever. Access tokens are only valid for one hour and you will need to request a new access token.
POST https://accounts.google.com/o/oauth2/token
client_id={ClientId}&client_secret={ClientSecret}&refresh_token=1/ffYmfI0sjR54Ft9oupubLzrJhD1hZS5tWQcyAvNECCA&grant_type=refresh_token
response
{
"access_token" : "ya29.1.AADtN_XK16As2ZHlScqOxGtntIlevNcasMSPwGiE3pe5ANZfrmJTcsI3ZtAjv4sDrPDRnQ",
"token_type" : "Bearer",
"expires_in" : 3600
}

Oauth 1.0a in Django

I'm trying to make authorised calls on the Rdio API in my Django application. I've been looking at the following tutorial so far to get it set up:
http://www.rdio.com/developers/docs/web-service/oauth/ref-oauth1-overview
The code at the bottom of the page works fine for me: I can get the request token, authorise the user using the PIN, and then make a call using the new access token.
However, I'd like to implement the callback so that the user can just log in and return to my site so that I can make authorised requests with their account. I currently have a page with a link to authorise the application, where the function to get the link is like so:
def get_auth_url():
client = oauth.Client(consumer)
response, content = client.request('http://api.rdio.com/oauth/request_token', 'POST', urllib.urlencode({'oauth_callback': 'http://localhost:8080/my_page/'}))
parsed_content = dict(cgi.parse_qsl(content))
request_token = oauth.Token(parsed_content['oauth_token'], parsed_content['oauth_token_secret'])
sURL = '%s?oauth_token=%s' % (parsed_content['login_url'], parsed_content['oauth_token'])
return sURL
This is okay, and when I click this link I go to a page asking to authorise my account for this application. However, I then need to get the access token from the request token that my user has just authorised. The callback from the authorisation page gives me oauth_verifier and oauth_token arguments but constructing the request token requires oauth_token and oauth_token_secret. I had the secret on the first call but can't get it again in this second call, and the tutorial said that I shouldn't store the secret anywhere accessible or transfer it across requests. And since these are two different requests I can't think of where to store the persistent request token. How can I get the oauth_token_secret on this second request so that I can get the access token?
You'll need to store the request token on your server temporarily so you can make the access token request. This line:
The request token secret must be included in the signature but not over the wire.
refers to the fact that the secret is used to generate the signature, but isn't included by itself in the request.
To save yourself some time and effort, I recommend using Django Social Auth. It already supports Rdio.

Cookie for Google App Engine request - Python

I'm working on an app which uses an API that requires me to make a first Post request to authenticate.
Looking the authenticate response, I've seen that a cookie was created: ApiCredentials=....
So I authenticate:
result = urlfetch.fetch(url = url, method = urlfetch.POST)
api_credential = result.headers['set-cookie']
and then I create a request with that cookie in the header
urlfetch.fetch(url = url, method = urlfetch.GET, headers = {'Cookie': api_credential})
The problem is: in dev everything works perfectly, but when I deploy, it doesn't work. In the logs I can see the cookie that was recieved.
API link: http://www.sptrans.com.br/desenvolvedores/APIOlhoVivo/Documentacao.aspx?1 (portuguese)
The code in the question does not show the cookie name ApiCredentials. It may be that in development, there was only a single cookie and in production there are more, in which case result.headers['set-cookie'] returns multiple values comma separated.
The URL Fetch Response Objects page suggests retrieving multiple headers of the same name in a list by calling result.header_msg.getheaders('set-cookie') instead and then finding ApiCredentials in the resulting list.
It might be safer to say 'Set-Cookie' in case-sensitive environments.

Categories

Resources