Single setup step in Locust 1.6.0? - python-3.x

I have been trying out how to setup a resource only once and share with user that gets created with locust
This is what I have been trying:
from locust import HttpUser
from locust.user.task import task
class WebsiteUser(HttpUser):
def setup(self):
client = clients.HttpSession(base_url=self.host)
token = client.post("get-token/", {'email': 'someemail', 'password': 'somepassword'})
client.headers.update(
{
'Authorization': 'Bearer ' + token
}
)
tasks = UserBehavior
min_wait = 500
max_wait = 900
As you see I wanted to fetch the token for once and share this with all other user instances that are created while load testing.
I can't figure out a way to achieve this.
Any sort of help would be really appreciated :)

You'll want to save the token in a variable accessible outside of the User to make it accessible by all users, otherwise each user would create its own.
from locust import HttpUser
from locust.user.task import task
token = ''
class WebsiteUser(HttpUser):
def setup(self):
global token
client = clients.HttpSession(base_url=self.host)
token = client.post("get-token/", {'email': 'someemail', 'password': 'somepassword'})
client.headers.update(
{
'Authorization': 'Bearer ' + token
}
)
tasks = UserBehavior
min_wait = 500
max_wait = 900
I'm not positive but you might also need to set the headers before each request instead of setting them once.

Related

Keycloak API :- Identify Users Realm for login

I am creating a microservice that will proxy keycloak for user creation, reset password, login etc. I don't want to expose any keycloak page like re-set password or login page so I am using the keycloak API and everything is fine so far.
The only issue is for login, where I need to know the realm to get the token as the API to get the token is realm specific.
realms/{REALM_NAME}/protocol/openid-connect/token
So is there a way to get a list of all the users from all the realms by admin user?
or any other way to find it?
You can get the realm information by decode user's access token.
The "iss" (issuer) claim identifies the principal that issued the JWT.
This is decode example by JWT.io
I demo make two realms (realm1 and realm2)
each realm add single user (both user same username: user and password: 1234)
And call get access token and decode it by Python
import requests
import ast
import jwt
def get_issuer(realm, user_name, password):
url = 'http://localhost:8180/auth/realms/'+realm+'/protocol/openid-connect/token'
body = {
'client_id': 'admin-cli',
'grant_type': 'password',
'username' : user_name,
'password': password
}
headers = {
'content-type': 'application/x-www-form-urlencoded'
}
response = requests.post(url, data=body, headers=headers).content.decode('utf-8')
token = ast.literal_eval(response)['access_token']
# print(token)
decoded = jwt.decode(token, options={"verify_signature": False})
# print(decoded)
return decoded['iss']
print('realm1 with user -->', get_issuer('realm1','user','1234'))
print('realm2 with user -->', get_issuer('realm2','user','1234'))
get this output
$python get_realm.py
realm1 with user --> http://localhost:8180/auth/realms/realm1
realm2 with user --> http://localhost:8180/auth/realms/realm2
If you want to get all users of realm,
you can get this API with master realm's admin token
GET /{realm}/users
After talking with No_One, I realize he want to get a relam name by username.
I made 600 realms and 3 users for each realm. So total user 1.8K and each user unique username. (If may same username, we can extend username and e-mail). I made python program to create relams and users.
So I demo search realm by username with for loop.
Check username exist for every realm
For loop all of relams
{keycloak_URL}/auth/admin/realms/{realm_name}/users/?username={user_name}
if you want to get list of realms,
{keycloak-url}/auth/admin/realms
The realm name format is realm_0xxx
example) realm_0001, realm_0002, ..., realm_0600
each ream has three users
example) In realm_0001,
user01_in_realm0001,
user02_in_realm0001,
user03_in_realm0001
In realm_0002,
user01_in_realm0002,
user02_in_realm0002,
user03_in_realm0002
...
In realm_0600,
user01_in_realm0600,
user02_in_realm0600,
user03_in_realm0600
This Python code search user by for loop
import admin
import random
admin = admin.Admin()
token = admin.get_master_token()
random_realm_num = random.randint(1, 600)
random_user_num = random.randint(1, 3)
realm_name = "realm_{:04d}".format(random_realm_num)
user_name = "user{:02d}_in_realm{:04d}".format(random_user_num, random_realm_num)
print('random realm_name:', realm_name)
print('random user_name:', user_name)
found = False
for realm_index in range(1,600,1):
realm_name = "realm_{:04d}".format(realm_index)
if(admin.is_user_exist(token, realm_name, user_name)):
print('user_name:', user_name,' belong to',realm_name)
found = True
break
if (not found):
print('user_name:', user_name,'is not belong to any realms')
This admin class
from urllib import response
from urllib.error import HTTPError
import requests
import ast
import json
class Admin:
# Keycloak master realm URL
url = 'http://localhost:8180/auth/realms/master/protocol/openid-connect/token'
# Keycloak master credential
params = {
'client_id': 'admin-cli',
'grant_type': 'password',
'username' : 'admin',
'password': 'admin'
}
def get_master_token(self):
try:
response = requests.post(self.url, self.params, verify=False).content.decode('utf-8')
except HTTPError as http_err:
print(f'HTTP error occurred: {http_err}') # Python 3.6
except Exception as err:
print(f'Other error occurred: {err}') # Python 3.6
print('Keycloak container is not running, Please check your docker container!')
raise SystemExit
else:
return ast.literal_eval(response)['access_token']
def is_user_exist(self, token, realm_name, user_name):
url ='http://localhost:8180/auth/admin/realms/'+realm_name+'/users/?username='+user_name.replace(" ", "%20")
headers = {
'content-type': 'application/json',
'Authorization' : 'Bearer '+ str(token)
}
try:
response = requests.get(url, headers=headers)
# print (response)
# print (response.content)
# If the response was successful, no Exception will be raised
response.raise_for_status()
except HTTPError as http_err:
print(f'HTTP error occurred: {http_err}') # Python 3.6
except Exception as err:
print(f'Other error occurred: {err}') # Python 3.6
else:
# print('Success!')
# print(response.text)
if len(response.content) == 2: # []
return False
if (json.loads(response.text)[0]['username'] == user_name.lower()):
return True
else:
return False
Result
random realm_name: realm_0430
random user_name: user03_in_realm0430
user_name: user03_in_realm0430 belong to realm_0430
[Done] exited with code=0 in 21.248 seconds
We also encountered such problem but we finally implemented by this way:
create a new SPI and provide a new rest endpoint like '/realm-list'
it will return a list of realms that doesn't require admin privilege to access
provide a page to list and choose your realm and than click a button
forward current page to login page(the realm will reprenset in url path)
one thing needs to note, the backend of keycloak needs to check if user is logged in, we add a new cookie value to mark whether the user is logged in.

Locust load testing For API calls

I have written a code for locust load testing for my case, where i can do a token call and then will make feature calls as per below code.
This is working fine with single token and 'n' number of users mentioned in master
I made a token call outside class and sending it as parameter to ENV. The userclass is reading the single token and using the same for all users.
I do not want to make token call inside the class, as it generates new token in every execution.
I'm looking if there is anyway like making token calls based on number of users mentioned on master -u and using only those tokens in User class.
Please suggest me if there is any documentation pointer which i can refer for this usecase
#! /usr/bin/python3.6
import json
from locust import HttpUser, task, constant, tag, events
from locust.log import setup_logging
import os
from datetime import datetime
import requests
setup_logging("INFO", None)
#events.init_command_line_parser.add_listener
def init_parser(parser):
parser.add_argument("--resp-delay", type=str, env_var="LOCUST_RESP_DELAY", default="", help="It's working")
parser.add_argument("--resp-size", type=str, env_var="LOCUST_RESP_SIZE", default="", help="It's working")
parser.add_argument("--env-endpoint", type=str, env_var="LOCUST_ENV_ENDPOINT", default="", help="It's working")
#events.init.add_listener
def _(environment, **kw):
os.environ['resp-delay'] = environment.parsed_options.resp_delay
os.environ['resp-size'] = environment.parsed_options.resp_size
os.environ['env-endpoint'] = environment.parsed_options.env_endpoint
with open("resources/data/" + environment.parsed_options.env_endpoint + '/data.json') as f:
data = json.load(f)
cal_transaction_id = datetime.now().strftime('%Y%m%dT%H%M%S')
#retrive cliend id and client secret from bitbucket repo
dict_car_app_all = data["data"]
print("env-endpoint:" + os.environ.get("env-endpoint"))
token_url = "https://ENDPOINT/auth/token"
#retrive cliend id and client secret from bitbucket repo
token_payload = "client_id=" + dict_car_app_all[0]["client_id"] + "&client_secret=" + dict_car_app_all[0]["client_secret"]
token_headers = {
'Content-Type': 'application/x-www-form-urlencoded'
}
response = requests.request("POST", token_url, data=token_payload, headers=token_headers,
cert=( 'resources/certs/' + environment.parsed_options.env_endpoint + '/MyCERT.pem',
'resources/certs/' + environment.parsed_options.env_endpoint + '/MYCERT.key'))
result = json.loads(response.text)
token = result["access_token"]
os.environ['access_token'] = token
os.environ['cal_transaction_id'] = cal_transaction_id
#class User_1(User):
class User_0(HttpUser):
wait_time = constant(1)
host = "host.com"
#tag('tag1')
#task
def load_test_api_tag1(self):
token_0 = os.environ.get('access_token')
cal_transaction_id = os.environ.get('cal_transaction_id')
env_endpoint = os.environ.get('env-endpoint')
resp_delay = os.environ.get("resp-delay")
resp_size = os.environ.get("resp-size")
feature_headers = {
'Authorization': "Bearer " + str(token_0),
'sm_transactionID': cal_transaction_id
}
url = "https://ENDPOINT/SERVICE/mytestservice/first_test"
querystring = {"response_delay": resp_delay, "data_size": resp_size}
self.client.request("GET", url, headers=feature_headers, params=querystring,
cert = ('resources/certs/' + env_endpoint + '/MyCERT.pem',
'resources/certs/' + env_endpoint + '/MyCERT.key'))
You can generate tokens in User class's on_start method so each user generates a new token when spawning.
class MyUser(User):
def on_start(self):
#generate token here and assign an instance variable like self.token=abc
super().on_start()
there is a drawback to this though, if your user count is more than your token generating service can handle some users will not be able to spawn, the way I do in my tests is if token generating part is not a part of the system I am testing, I generate tokens beforehand and write it in some file or some external db and read them from there.

How to use Cognito for AppSync mutation call (Python)

I'd like to call mutations from AppSync using my Python function but use a Cognito user for the authorization as "API-KEY", "IAM" and other methods are not suitable for my application.
My mutation looks like this (test purposes):
mutation XYZ {
updateTask(input: {id: "a1b2c3", name: "newTaskName"}) {
id
name
}
}
I am assuming that the user is already created and enabled by some means. If your AppSync API is secured only using Cognito, you are always going to need a username and a password to begin with. For example, you can use below code to login and get the AccessToken from the response:
import boto3
def get_user_auth(event, context):
client = boto3.client('cognito-idp')
response = client.initiate_auth(
UserPoolId='xxxxxxxxx',
ClientId='xxxxxxxxxxxxxx',
AuthFlow='USER_PASSWORD_AUTH',
AuthParameters={
'USERNAME': 'xxxxxx',
'PASSWORD': 'xxxxxx'
}
)
return response
Note: Make sure that you have "Enable username password based authentication (ALLOW_USER_PASSWORD_AUTH)" enabled.
Once you have the access token, you can use this in HTTP headers within your request as follows:
{
"authorization": "<YOUR-VERY-VERY-LONG-ACCESS-TOKEN>"
}
For example:
import requests
from requests_aws4auth import AWS4Auth
import boto3
session = requests.Session()
APPSYNC_API_ENDPOINT_URL = '<YOUR-API-URL>'
mutation = """mutation XYZ {updateTask(input: {id: "a1b2c3", name: "newTaskName"}) {id, name}}"""
response = session.request(
url=APPSYNC_API_ENDPOINT_URL,
method='POST',
headers={'authorization': '<YOUR-VERY-VERY-LONG-ACCESS-TOKEN>'},
json={'mutation': mutation}
)
print(response.json()['data'])
Since this access token has some expiration, you might also need to refresh this token by using the RefreshToken from the above response. Like so:
def refresh_token(self, username, refresh_token):
try:
return client.initiate_auth(
ClientId=self.client_id,
AuthFlow='REFRESH_TOKEN_AUTH',
AuthParameters={
'REFRESH_TOKEN': refresh_token,
# 'SECRET_HASH': self.get_secret_hash(username)
# If the User Pool has been defined with App Client secret,
# you will have to generate secret hash as well.
}
)
except botocore.exceptions.ClientError as e:
return e.response
Example of how you can generate secret hash.

How to send a GraphQL query to AppSync from python?

How do we post a GraphQL request through AWS AppSync using boto?
Ultimately I'm trying to mimic a mobile app accessing our stackless/cloudformation stack on AWS, but with python. Not javascript or amplify.
The primary pain point is authentication; I've tried a dozen different ways already. This the current one, which generates a "401" response with "UnauthorizedException" and "Permission denied", which is actually pretty good considering some of the other messages I've had. I'm now using the 'aws_requests_auth' library to do the signing part. I assume it authenticates me using the stored /.aws/credentials from my local environment, or does it?
I'm a little confused as to where and how cognito identities and pools will come into it. eg: say I wanted to mimic the sign-up sequence?
Anyways the code looks pretty straightforward; I just don't grok the authentication.
from aws_requests_auth.boto_utils import BotoAWSRequestsAuth
APPSYNC_API_KEY = 'inAppsyncSettings'
APPSYNC_API_ENDPOINT_URL = 'https://aaaaaaaaaaaavzbke.appsync-api.ap-southeast-2.amazonaws.com/graphql'
headers = {
'Content-Type': "application/graphql",
'x-api-key': APPSYNC_API_KEY,
'cache-control': "no-cache",
}
query = """{
GetUserSettingsByEmail(email: "john#washere"){
items {name, identity_id, invite_code}
}
}"""
def test_stuff():
# Use the library to generate auth headers.
auth = BotoAWSRequestsAuth(
aws_host='aaaaaaaaaaaavzbke.appsync-api.ap-southeast-2.amazonaws.com',
aws_region='ap-southeast-2',
aws_service='appsync')
# Create an http graphql request.
response = requests.post(
APPSYNC_API_ENDPOINT_URL,
json={'query': query},
auth=auth,
headers=headers)
print(response)
# this didn't work:
# response = requests.post(APPSYNC_API_ENDPOINT_URL, data=json.dumps({'query': query}), auth=auth, headers=headers)
Yields
{
"errors" : [ {
"errorType" : "UnauthorizedException",
"message" : "Permission denied"
} ]
}
It's quite simple--once you know. There are some things I didn't appreciate:
I've assumed IAM authentication (OpenID appended way below)
There are a number of ways for appsync to handle authentication. We're using IAM so that's what I need to deal with, yours might be different.
Boto doesn't come into it.
We want to issue a request like any regular punter, they don't use boto, and neither do we. Trawling the AWS boto docs was a waste of time.
Use the AWS4Auth library
We are going to send a regular http request to aws, so whilst we can use python requests they need to be authenticated--by attaching headers.
And, of course, AWS auth headers are special and different from all others.
You can try to work out how to do it
yourself, or you can go looking for someone else who has already done it: Aws_requests_auth, the one I started with, probably works just fine, but I have ended up with AWS4Auth. There are many others of dubious value; none endorsed or provided by Amazon (that I could find).
Specify appsync as the "service"
What service are we calling? I didn't find any examples of anyone doing this anywhere. All the examples are trivial S3 or EC2 or even EB which left uncertainty. Should we be talking to api-gateway service? Whatsmore, you feed this detail into the AWS4Auth routine, or authentication data. Obviously, in hindsight, the request is hitting Appsync, so it will be authenticated by Appsync, so specify "appsync" as the service when putting together the auth headers.
It comes together as:
import requests
from requests_aws4auth import AWS4Auth
# Use AWS4Auth to sign a requests session
session = requests.Session()
session.auth = AWS4Auth(
# An AWS 'ACCESS KEY' associated with an IAM user.
'AKxxxxxxxxxxxxxxx2A',
# The 'secret' that goes with the above access key.
'kwWxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxgEm',
# The region you want to access.
'ap-southeast-2',
# The service you want to access.
'appsync'
)
# As found in AWS Appsync under Settings for your endpoint.
APPSYNC_API_ENDPOINT_URL = 'https://nqxxxxxxxxxxxxxxxxxxxke'
'.appsync-api.ap-southeast-2.amazonaws.com/graphql'
# Use JSON format string for the query. It does not need reformatting.
query = """
query foo {
GetUserSettings (
identity_id: "ap-southeast-2:8xxxxxxb-7xx4-4xx4-8xx0-exxxxxxx2"
){
user_name, email, whatever
}}"""
# Now we can simply post the request...
response = session.request(
url=APPSYNC_API_ENDPOINT_URL,
method='POST',
json={'query': query}
)
print(response.text)
Which yields
# Your answer comes as a JSON formatted string in the text attribute, under data.
{"data":{"GetUserSettings":{"user_name":"0xxxxxxx3-9102-42f0-9874-1xxxxx7dxxx5"}}}
Getting credentials
To get rid of the hardcoded key/secret you can consume the local AWS ~/.aws/config and ~/.aws/credentials, and it is done this way...
# Use AWS4Auth to sign a requests session
session = requests.Session()
credentials = boto3.session.Session().get_credentials()
session.auth = AWS4Auth(
credentials.access_key,
credentials.secret_key,
boto3.session.Session().region_name,
'appsync',
session_token=credentials.token
)
...<as above>
This does seem to respect the environment variable AWS_PROFILE for assuming different roles.
Note that STS.get_session_token is not the way to do it, as it may try to assume a role from a role, depending where it keyword matched the AWS_PROFILE value. Labels in the credentials file will work because the keys are right there, but names found in the config file do not work, as that assumes a role already.
OpenID
In this scenario, all the complexity is transferred to the conversation with the openid connect provider. The hard stuff is all the auth hoops you jump through to get an access token, and thence using the refresh token to keep it alive. That is where all the real work lies.
Once you finally have an access token, assuming you have configured the "OpenID Connect" Authorization Mode in appsync, then you can, very simply, drop the access token into the header:
response = requests.post(
url="https://nc3xxxxxxxxxx123456zwjka.appsync-api.ap-southeast-2.amazonaws.com/graphql",
headers={"Authorization": ACCESS_TOKEN},
json={'query': "query foo{GetStuff{cat, dog, tree}}"}
)
You can set up an API key on the AppSync end and use the code below. This works for my case.
import requests
# establish a session with requests session
session = requests.Session()
# As found in AWS Appsync under Settings for your endpoint.
APPSYNC_API_ENDPOINT_URL = 'https://vxxxxxxxxxxxxxxxxxxy.appsync-api.ap-southeast-2.amazonaws.com/graphql'
# setup the query string (optional)
query = """query listItemsQuery {listItemsQuery {items {correlation_id, id, etc}}}"""
# Now we can simply post the request...
response = session.request(
url=APPSYNC_API_ENDPOINT_URL,
method='POST',
headers={'x-api-key': '<APIKEYFOUNDINAPPSYNCSETTINGS>'},
json={'query': query}
)
print(response.json()['data'])
Building off Joseph Warda's answer you can use the class below to send AppSync commands.
# fileName: AppSyncLibrary
import requests
class AppSync():
def __init__(self,data):
endpoint = data["endpoint"]
self.APPSYNC_API_ENDPOINT_URL = endpoint
self.api_key = data["api_key"]
self.session = requests.Session()
def graphql_operation(self,query,input_params):
response = self.session.request(
url=self.APPSYNC_API_ENDPOINT_URL,
method='POST',
headers={'x-api-key': self.api_key},
json={'query': query,'variables':{"input":input_params}}
)
return response.json()
For example in another file within the same directory:
from AppSyncLibrary import AppSync
APPSYNC_API_ENDPOINT_URL = {YOUR_APPSYNC_API_ENDPOINT}
APPSYNC_API_KEY = {YOUR_API_KEY}
init_params = {"endpoint":APPSYNC_API_ENDPOINT_URL,"api_key":APPSYNC_API_KEY}
app_sync = AppSync(init_params)
mutation = """mutation CreatePost($input: CreatePostInput!) {
createPost(input: $input) {
id
content
}
}
"""
input_params = {
"content":"My first post"
}
response = app_sync.graphql_operation(mutation,input_params)
print(response)
Note: This requires you to activate API access for your AppSync API. Check this AWS post for more details.
graphql-python/gql supports AWS AppSync since version 3.0.0rc0.
It supports queries, mutation and even subscriptions on the realtime endpoint.
The documentation is available here
Here is an example of a mutation using the API Key authentication:
import asyncio
import os
import sys
from urllib.parse import urlparse
from gql import Client, gql
from gql.transport.aiohttp import AIOHTTPTransport
from gql.transport.appsync_auth import AppSyncApiKeyAuthentication
# Uncomment the following lines to enable debug output
# import logging
# logging.basicConfig(level=logging.DEBUG)
async def main():
# Should look like:
# https://XXXXXXXXXXXXXXXXXXXXXXXXXX.appsync-api.REGION.amazonaws.com/graphql
url = os.environ.get("AWS_GRAPHQL_API_ENDPOINT")
api_key = os.environ.get("AWS_GRAPHQL_API_KEY")
if url is None or api_key is None:
print("Missing environment variables")
sys.exit()
# Extract host from url
host = str(urlparse(url).netloc)
auth = AppSyncApiKeyAuthentication(host=host, api_key=api_key)
transport = AIOHTTPTransport(url=url, auth=auth)
async with Client(
transport=transport, fetch_schema_from_transport=False,
) as session:
query = gql(
"""
mutation createMessage($message: String!) {
createMessage(input: {message: $message}) {
id
message
createdAt
}
}"""
)
variable_values = {"message": "Hello world!"}
result = await session.execute(query, variable_values=variable_values)
print(result)
asyncio.run(main())
I am unable to add a comment due to low rep, but I just want to add that I tried the accepted answer and it didn't work. I was getting an error saying my session_token is invalid. Probably because I was using AWS Lambda.
I got it to work pretty much exactly, but by adding to the session token parameter of the aws4auth object. Here's the full piece:
import requests
import os
from requests_aws4auth import AWS4Auth
def AppsyncHandler(event, context):
# These are env vars that are always present in an AWS Lambda function
# If not using AWS Lambda, you'll need to add them manually to your env.
access_id = os.environ.get("AWS_ACCESS_KEY_ID")
secret_key = os.environ.get("AWS_SECRET_ACCESS_KEY")
session_token = os.environ.get("AWS_SESSION_TOKEN")
region = os.environ.get("AWS_REGION")
# Your AppSync Endpoint
api_endpoint = os.environ.get("AppsyncConnectionString")
resource = "appsync"
session = requests.Session()
session.auth = AWS4Auth(access_id,
secret_key,
region,
resource,
session_token=session_token)
The rest is the same.
Hope this Helps Everyone
import requests
import json
import os
from dotenv import load_dotenv
load_dotenv(".env")
class AppSync(object):
def __init__(self,data):
endpoint = data["endpoint"]
self.APPSYNC_API_ENDPOINT_URL = endpoint
self.api_key = data["api_key"]
self.session = requests.Session()
def graphql_operation(self,query,input_params):
response = self.session.request(
url=self.APPSYNC_API_ENDPOINT_URL,
method='POST',
headers={'x-api-key': self.api_key},
json={'query': query,'variables':{"input":input_params}}
)
return response.json()
def main():
APPSYNC_API_ENDPOINT_URL = os.getenv("APPSYNC_API_ENDPOINT_URL")
APPSYNC_API_KEY = os.getenv("APPSYNC_API_KEY")
init_params = {"endpoint":APPSYNC_API_ENDPOINT_URL,"api_key":APPSYNC_API_KEY}
app_sync = AppSync(init_params)
mutation = """
query MyQuery {
getAccountId(id: "5ca4bbc7a2dd94ee58162393") {
_id
account_id
limit
products
}
}
"""
input_params = {}
response = app_sync.graphql_operation(mutation,input_params)
print(json.dumps(response , indent=3))
main()

Flask JWT Extended cookie name overide Flask Session Cookie Name

I am using the Flask JWT Extended extension for flask and have built a login application successfully using JWT. I have gone through the tutorial on JWT in Cookies on the JWT extended documentation site using CSRF protection and everything.
What I can't seem to figure out is when using the set_access_cookies() and set_refresh_cookies() methods the JWTs are not saved in a httponly cookie named using the JWT Extended default configuration setting.
app.config.setdefault('JWT_ACCESS_COOKIE_NAME', 'access_token_cookie')
app.config.setdefault('JWT_REFRESH_COOKIE_NAME', 'refresh_token_cookie')
Instead when I debug the return back from the auth call the cookies are saved in the base Flask default configuration instead.
'SESSION_COOKIE_NAME': 'session',
Shouldn't the set_access_cookies() and set_refresh_cookies() methods override the base Flask default configurations as long as make sure to register my app in the JWTManager()?
uscc_login_app = Flask(__name__)
jwt = JWTManager(uscc_login_app)
Or is there something else I missed in the base Flask JWT Extended documentation to ensure that its configuration defaults are used when appropriate?
Updated code via request.
The code is pretty spread out but here is my best shot to include what I think will help.
In init.py:
from flask import Flask, url_for
from flask_restful import Api
from flask_jwt_extended import JWTManager
from resources.auth import Authenticate
from resources.refresh import Refresh
from temp_app.temp import TempApp
from uscc_login.uscc_app_login import *
uscc_login_app = Flask(__name__)
uscc_login_app.config.from_object(os.environ.get('FLASK_ENV'))
jwt = JWTManager(uscc_login_app)
api = Api(uscc_login_app, prefix='/v1')
# Add resources via the add_resource method
api.add_resource(Authenticate, '/login')
api.add_resource(Refresh, '/refresh_token')
login_view = Login.as_view(name='uscc_login')
uscc_login_app.add_url_rule('/login', view_func=login_view, methods=['POST', 'GET'])
In my app.py:
from uscc_login import uscc_login_app
if __name__ == '__main__':
uscc_login_app.run(debug=uscc_login_app.config.get('DEBUG'), threaded=uscc_login_app.config.get('THREADED'),
port=uscc_login_app.config.get('PORT'), host=uscc_login_app.config.get('HOST'))
In my config.py since I am using the Flask config.from_objects
import os
import datetime
uscc_login_app_dir = os.path.abspath(os.path.dirname(__file__))
class BaseConfig:
SECRET_KEY = os.environ.get('USCC_SECRET_KEY') or 'you-will-never-guess'
JWT_SECRET_KEY = os.environ.get('USCC_JWT_KEY') or 'super-secret'
JWT_TOKEN_LOCATION = ['cookies']
JWT_COOKIE_CSRF_PROTECT = True
JWT_HEADER_TYPE = 'JWT'
PROPAGATE_EXCEPTIONS = True
THREADED = True
class DevelopmentConfig(BaseConfig):
DEBUG = True
PORT = 5000 if os.environ.get("PORT") is None else int(os.environ.get("PORT"))
HOST = os.environ.get('HOST') or 'localhost'
if os.environ.get('access_token_expiration') is not None:
JWT_ACCESS_TOKEN_EXPIRES = datetime.timedelta(seconds=int(os.environ.get('access_token_expiration')))
if os.environ.get('refresh_token_expiration') is not None:
JWT_REFRESH_TOKEN_EXPIRES = datetime.timedelta(seconds=int(os.environ.get('refresh_token_expiration')))
So then in my Flask MethodView that contains my login authorization POST I have the following:
auth.py
import sys
import os
from flask import jsonify, request
from flask_restful import Resource
from flask_jwt_extended import create_access_token, create_refresh_token, jwt_refresh_token_required, get_jwt_identity, \
set_access_cookies, set_refresh_cookies
from utilities import Common
class Authenticate(Resource):
#staticmethod
def post():
"""
:return:
"""
api_cred_path = os.environ.get('api_cred_path')
if api_cred_path is None:
response = jsonify({"msg": "Environment Variable 'api_cred_path' is not set."})
response.status_code = 500
return response
if not request.is_json:
response = jsonify({'msg': 'Missing JSON in request'})
response.status_code = 400
return response
params = request.get_json()
user_name = params.get('username')
user_password = params.get('password')
if not user_name:
response = jsonify({'msg': 'Missing username parameter'})
response.status_code = 400
return response
if not user_password:
response = jsonify({'msg': 'Missing password parameter'})
response.status_code = 400
return response
if Common.check_path_exists(api_cred_path):
with open(api_cred_path) as afh:
for line in afh:
file_userid, file_password = line.split('=')
if file_userid == user_name and file_password.strip('\n') == user_password:
access_token = create_access_token(identity=user_name)
refresh_token = create_refresh_token(identity=user_name)
response = jsonify({'login': True})
set_access_cookies(response, access_token)
set_refresh_cookies(response, refresh_token)
# # Identity can be any data that is json serializable
# art = {
# 'access_token': create_access_token(identity=user_name),
# 'refresh_token': create_refresh_token(identity=user_name)}
# response = jsonify(art)
response.status_code = 200
return response
else:
response = jsonify({"msg": "api_cred_path invalid."})
response.status_code = 500
return response
response = jsonify({'msg': 'Bad username or password'})
response.status_code = 401
return response
Could you provide some code to duplicate what you are seeing? When I try running the example token in jwt code (https://github.com/vimalloc/flask-jwt-extended/blob/master/examples/jwt_in_cookie.py) I see the expected cookie values when I login:
$ http :5000/token/auth username=test password=test
...
Set-Cookie: access_token_cookie=<jwt>; HttpOnly; Path=/api/
Set-Cookie: refresh_token_cookie=<jwt>; HttpOnly; Path=/token/refresh
...
So I realized my mistake in this. I was trying to get the access_token_cookie variable to be set from my auth.py which serves as my RESTFUL based microservice of which my login app calls to do the authorization. Realized it won't be available after redirecting back to the caller from the login apps POST method since the cookie was related to the login app UI frontend. So I just base the access and refresh tokens back from the auth.py POST method to the login POST method and let it set the cookies so they are available to the end client.
This was more of design problem than a code problem.

Resources