I am trying to integrate oauth2 with fastapi running with mock oidc-server authentication. I went through the documentation but not able to make out what fits where. This is a snippet from two files -
main.py
from authlib.integrations.starlette_client import OAuth
oauth = OAuth()
CONF_URL = "https://localhost:8080/.well-known/openid-configuration"
oauth.register(
name="cad",
server_metadata_url=CONF_URL,
client_id=settings.CLIENT_ID,
client_secret=settings.CLIENT_SECRET,
client_kwargs={"scope": "openid email profile authorization_group"},
)
#app.middleware("http")
async def authorize(request: Request, call_next):
if not (request.scope["path"].startswith("/login") or request.scope["path"].startswith("/auth")):
if not is_session_okay(request.session):
return RedirectResponse(url="/login")
return await call_next(request)
#app.get("/login")
async def login(request: Request):
redirect_uri = request.url_for("auth")
return await oauth.cad.authorize_redirect(request, redirect_uri)
#app.get("/auth")
async def auth(request: Request):
try:
token = await oauth.cad.authorize_access_token(request)
except OAuthError as error:
return HTMLResponse(f"<h1>{error.error}</h1>")
user = await oauth.cad.parse_id_token(request, token)
request.session["user"] = dict(user)
request.session["session_expiry"] = str(datetime.datetime.utcnow() +
datetime.timedelta(hours=48))
return {"access_token": create_token(user['sub'), "token_type": "bearer"}
& jwt.py
oauth2_scheme = OAuth2PasswordBearer(tokenUrl='/auth', auto_error=False)
# Error
CREDENTIALS_EXCEPTION = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail='Could not validate credentials',
headers={'WWW-Authenticate': 'Bearer'},
)
# Create token internal function
def create_access_token(*, data: dict, expires_delta: timedelta = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({'exp': expire})
encoded_jwt = jwt.encode(to_encode, API_SECRET_KEY, algorithm=API_ALGORITHM)
return encoded_jwt
def create_refresh_token(email):
expires = timedelta(minutes=REFRESH_TOKEN_EXPIRE_MINUTES)
return create_access_token(data={'sub': email}, expires_delta=expires)
def create_token(id):
access_token_expires = timedelta(minutes=API_ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(data={'sub': id}, expires_delta=access_token_expires)
return access_token
async def get_current_user(token: str = Depends(oauth2_scheme)):
try:
payload = decode_token(token)
id: str = payload.get('sub')
if email is None:
raise CREDENTIALS_EXCEPTION
except jwt.JWTError:
raise CREDENTIALS_EXCEPTION
raise id
I have tried integrating create_token in the "auth" endpoint and adding Depends(get_current_user) parameter in get api . I get the authorize button in swagger, but authorization doesn't happen with client id & client secret, nor with user-name & passwd.
In my main.py, I have the following code-
app = FastAPI(docs_url="",)
app.add_middleware(SessionMiddleware, secret_key=os.getenv('SECRET'))
config = Config('.env')
oauth = OAuth(config)
CONF_URL = 'http://localhost:9090/.well-known/openid-configuration'
oauth.register(
name='google',
server_metadata_url=CONF_URL,
client_id=os.getenv('ID'),
client_secret=os.getenv('SECRET'),
client_kwargs={
'scope': 'openid email profile'
}
)
api_url = None
#app.get('/')
async def homepage(request: Request):
user = request.session.get('user')
if user:
data = json.dumps(user)
html = (
f'<pre>{data}</pre>'
'logout'
)
return HTMLResponse(html)
return HTMLResponse('login')
#app.get('/login')
async def login(request: Request):
redirect_uri = request.url_for('auth')
return await oauth.google.authorize_redirect(request, redirect_uri)
#app.get('/auth')
async def auth(request: Request):
try:
token = await oauth.google.authorize_access_token(request)
except OAuthError as error:
return HTMLResponse(f'<h1>{error.error}</h1>')
user = await oauth.google.parse_id_token(request, token)
request.session['user'] = dict(user)
if api_url:
return RedirectResponse(url=api_url)
return RedirectResponse(url='/')
#app.get('/logout')
async def logout(request: Request):
request.session.pop('user', None)
request.cookies.clear()
return RedirectResponse(url='/')
#app.get("/api")
async def get_swagger_ui(request: Request):
global api_url
api_url = request.url
user = request.session.get('user')
if user:
return get_swagger_ui_html(
openapi_url="/openapi.json", title="Webapp",)
else:
return RedirectResponse(
url="/login"
)
# routes
PROTECTED = [Depends(login)]
app.include_router(get_api.router, dependencies=PROTECTED)
In the get_api.py file, I have the following conf -
router = APIRouter()
#router.get("/api/higs", tags=["higs"])
def get_higs(db: Session = Depends(get_db),
)
try:
<something>
return x
except Exception as err:
raise HTTPException(
status_code=400, detail="Invalid parameter : {}".format(err),
)
There are similar other endpoints in the get_api.py file. I wanted to block access to these endpoints without authentication. So in the app.include_router method, I added dependencies. But its not working. I am able to access the endpoint data. For e.g. localhost:8000/api/higs - displays all the data in text that I would get from calling executing the GET endpoint in swagger UI. How can I fix this issue. Thanks.
I am following a tutorial from CodingEntrepreneurs and i have come across a road bump where it returns a 400 error when i run it.
Here is my code
import base64, requests
import datetime
from urllib.parse import urlencode
client_id = "my id"
client_secret = "my secret"
class SpotifyAPI(object):
access_token = None
access_token_expires = datetime.datetime.now()
access_token_did_expire = True
client_id = None
client_secret = None
token_url = "https://accounts.spotify.com/api/token"
def __init__(self, client_id, client_secret, *args, **kwargs):
super().__init__(*args, **kwargs)
self.client_id = client_id
self.client_secret = client_secret
def getClientCreds(self):
'''Returns b64 encoded string'''
client_id = self.client_id
client_secret = self.client_secret
if client_id == None or client_secret == None:
raise Exception('Must set a client id and secret')
client_creds = f"{client_id}:{client_secret}"
client_creds_b64 = base64.b64encode(client_creds.encode())
return client_creds_b64.decode()
def getTokenHeader(self):
client_creds_b64 = self.getClientCreds()
return {
'Authorization':f"Basic {client_creds_b64}"
}
def getTokenData(self):
return {
"grant_type":"client_credentials"
}
def perform_auth(self):
token_url = self.token_url
token_data = self.getTokenData()
token_header = self.getTokenHeader()
r = requests.post(token_url, data=token_data, headers=token_header)
if r.status_code not in range(200,299):
return False
now = datetime.datetime.now()
token_response_data = r.json()
access_token = token_response_data['access_token']
expires_in = token_response_data['expires_in']
expires = now + datetime.timedelta(seconds=expires_in)
self.access_token = access_token
self.access_token_expires = expires
self.access_token_did_expire = expires < now
return True
spotify = SpotifyAPI(client_id, client_secret)
print(spotify.perform_auth())
token = spotify.access_token
header = {
"Authorization": f"Bearer{token}",
}
endpoint = "https://api.spotify.com/v1/search"
data = urlencode({"q": "Time", "type": "track"})
lookupURL = f"{endpoint}?{data}"
r = requests.get(lookupURL, headers=header)
print(r.json())
When i run this it returns this
"
True
{'error': {'status': 400, 'message': 'Only valid bearer authentication supported'}}
"
Please could someone help and explain the solution.
Thanks,
Sam :)
I think it could be a problem here, as you are leaving no space between the Bearer keyword and the token:
# previous
header = {
"Authorization": f"Bearer{token}",
}
# correct
header = {
"Authorization": f"Bearer {token}",
}
i want to isolate each socket connection and emit to that specific connection, when i try to run without threading, it works fine and each socket gets it's own messages, but when i use threading, it just starts broadcasting and all the clients receive all the messages.
Here's how my client side looks like :
var socket = io.connect('https://my_server_link');
socket.on('connect', function() {
console.log('Connection Success')
});
// Recieves the session id
socket.on('connection_status', (data)=>{
console.log('Connection Status')
console.log(data);
session_id = data['session_id']
socket.emit('verifyClient', {'session_id': session_id, 'api_id': api_id})
})
socket.on('verification_status', (data)=>{
console.log('Verification Status')
console.log(data)
if (data['status']){
console.log('Verified')
socket.emit('setupUser', {'session_id': session_id, 'user_id': user_id})
}
})
socket.on('user_setup_status', (data)=>{
console.log('User Setup Status');
console.log(data);
if (data['status']){
console.log('User Setup Successful, Starting monitoring...')
running_ops = setInterval(()=>{
op_interval(session_id)
}, 5000)
}
})
function op_interval(session_id){
ctx2.drawImage(video, 0, 0, video.width, video.height);
frame = canva.toDataURL('image/jpeg');
frame = to_blob(frame);
socket.emit('monitor', {
'frame':frame,
'session_id':session_id
});
}
// when error messages are encountered, generate alert on client side
socket.on('alert', (alert)=>{
console.log(alert);
// TODO : Handle Alerts Here
})
And here's my server side.
app = Flask(__name__)
CORS(app, resources={"*": {"origins": "*"}})
# eventlet.monkey_patch()
socketio = SocketIO(app, async_mode='threading', cors_allowed_origins="*")
# Set up socket connection to the client and register session id
#socketio.on('connect')
def handle_connection():
print('Connection Successfull')
# connection_id = request.sid
session_id = ''.join(random.choice(string.ascii_lowercase) for i in range(10))
active_socket_sessions[session_id] = {'session_id': session_id}#, 'connection_id': connection_id}
join_room(session_id)
emit('connection_status', {'status':'success', 'session_id':session_id}, room=session_id)
#socketio.on('verifyClient')
def verification(data):
session_id = data['session_id']
client_id = data['api_id']
verified = False
message = "Session not found."
if session_id in active_socket_sessions.keys():
client, message = verify_client(database, client_id, active_sessions)
if len(client.keys()) > 0:
verified = True
active_socket_sessions[session_id]['client_id'] = client['client_id']
emit('verification_status', {'status':verified, 'message': message, 'session_id':session_id}, room=session_id)
else:
emit('verification_status', {'status':verified, 'message': message, 'session_id':session_id})
#socketio.on('setupUser')
def user_setup(data):
session_id = data['session_id']
user_id = data['user_id']
found = False
message = 'Session not found.'
if session_id in active_socket_sessions.keys():
client_id = active_socket_sessions[session_id]['client_id']
user, message = get_user(database, {'client_id': client_id}, user_id)
if len(user.keys()) > 0:
found = True
user['person_encoding'] = str(user['person_encoding'])
active_socket_sessions[session_id]['user'] = user
rediss.set_dict(session_id, active_socket_sessions[session_id])
active_socket_sessions[session_id] = {}
emit('user_setup_status', {'status': found, 'message': message, 'session_id': session_id}, room=session_id)
else:
emit('user_setup_status', {'status': found, 'message': message, 'session_id': session_id})
#socketio.on('disconnect')
def handle_disconnect():
# Handle Clearing Of Socket From Redis And Also Closing The Socket Connection
emit('connection_ended', {'data': 'Successfully Disconnected, Socket Is Closed.'})
#socketio.on('monitor')
def monitor(data):
frame = data['frame']
session_id = data['session_id']
socketio.start_background_task(monitoring_process, frame, session_id)
def monitoring_process(frame, session_id):
if session_id in active_socket_sessions.keys():
session = rediss.get_dict(session_id)
client_id = session['client_id']
user = session['user']
user_encoding = np.array(eval(user['person_encoding']))
person_id = user['person_id']
browser_info = {}
frame = cv2.imdecode(np.frombuffer(frame, np.uint8), -1)
preds = model.model.predict(frame, user_encoding)
if not preds['recognized']:
alert = vars.BRISO_ALERTS[preds['message']]
alert['timestamp'] = str(time.time())
alert['log_id'] = ''.join(random.choice(string.ascii_lowercase) for i in range(10))
alert['person_id'] = person_id
headers = browser_info
message, flag = log_alert(database, client_id, alert, headers)
preds['session_id'] = session_id
socketio.emit('alert', preds, room=session_id)
if __name__ == "__main__":
socketio.run(app, host=vars.HOST_NAME, port=vars.SERVER_PORT, debug=False, use_reloader=False)
Ok, so as suggested by Miguel Grinberg himself, when i asked him on github, the only replacement i had to make was to switch from setting my own session_id to request.sid and not calling the join_room method.
The updated (working) code looks like this :
For client side socket.io:
socket.on('connect', function() {
console.log('Connection Success')
});
// Recieves the session id
socket.on('connection_status', (data)=>{
console.log('Connection Status')
console.log(data);
session_id = data['session_id']
socket.emit('verify_client', {'session_id': session_id, 'api_id': api_id}, (status)=>{
socket.emit('setup_user', {'session_id': session_id, 'user_id': user_id}, (status)=>{
running_ops = setInterval(()=>{
op_interval(session_id)
}, 5000)
})
})
})
function op_interval(session_id){
ctx2.drawImage(video, 0, 0, video.width, video.height);
frame = canva.toDataURL('image/jpeg');
frame = to_blob(frame);
socket.emit('monitor', {
'frame':frame,
'session_id':session_id
});
}
// when error messages are encountered, generate alert on client side
socket.on('alert', (alert)=>{
console.log(alert);
})
for python flask socketio
# Set up socket connection to the client and register session id
#socketio.on('connect')
def handle_connection():
print('Connection Successfull')
session_id = request.sid
active_socket_sessions[session_id] = {'session_id': session_id}#, 'connection_id': connection_id}
emit('connection_status', {'status':'success', 'session_id':session_id}, room=session_id)
#socketio.on('verify_client')
def verification(data):
session_id = request.sid
client_id = data['api_id']
verified = False
message = "Session not found."
if session_id in active_socket_sessions.keys():
client, message = verify_client(database, client_id, active_sessions)
if len(client.keys()) > 0:
verified = True
active_socket_sessions[session_id]['client_id'] = client['client_id']
return json.dumps({'status': verified, 'message': message, 'session_id': session_id})
else:
return json.dumps({'status': verified, 'message': message, 'session_id': session_id})
#socketio.on('setup_user')
def user_setup(data):
session_id = request.sid
user_id = data['user_id']
found = False
message = 'Session not found.'
if session_id in active_socket_sessions.keys():
client_id = active_socket_sessions[session_id]['client_id']
user, message = get_user(database, {'client_id': client_id}, user_id)
if len(user.keys()) > 0:
found = True
user['person_encoding'] = str(user['person_encoding'])
active_socket_sessions[session_id]['user'] = user
rediss.set_dict(session_id, active_socket_sessions[session_id])
active_socket_sessions[session_id] = {}
return json.dumps({'status': found, 'message': message, 'session_id': session_id})
else:
return json.dumps({'status': found, 'message': message, 'session_id': session_id})
#socketio.on('disconnect')
def handle_disconnect():
# Handle Clearing Of Socket From Redis And Also Closing The Socket Connection
emit('connection_ended', {'data': 'Successfully Disconnected, Socket Is Closed.'})
#socketio.on('monitor')
def monitor(data):
frame = data['frame']
session_id = request.sid
process = Thread(target=monitoring_process, args=(frame, session_id,))
process.start()
process.join()
socketio.start_background_task(monitoring_process, frame, session_id)
def monitoring_process(frame, session_id):
if session_id in active_socket_sessions.keys():
session = rediss.get_dict(session_id)
client_id = session['client_id']
user = session['user']
user_encoding = np.array(eval(user['person_encoding']))
person_id = user['person_id']
browser_info = {}
frame = cv2.imdecode(np.frombuffer(frame, np.uint8), -1)
preds = model.model.predict(frame, user_encoding)
if not preds['recognized']:
alert = vars.ALERTS[preds['message']]
alert['timestamp'] = str(time.time())
alert['log_id'] = ''.join(random.choice(string.ascii_lowercase) for i in range(10))
alert['person_id'] = person_id
headers = browser_info
message, flag = log_alert(database, client_id, alert, headers)
preds['session_id'] = session_id
# return preds
socketio.emit('alert', preds, room=session_id)
Currently my API requires to use Token authentication.
POST /api/authorize HTTP/1.1
"version": "20151130", // The version of the REST API you are using
"client_id": "01234567890123456789", // Your 20 char public client id
"client_secret": "0123456789..." // Your 40 char client secret
I get the response:
{
"auth_token": "abcdef...",
"auth_expires": "20180524T062442Z"
}
My current pattern is like this:
I have a list of items I need to pass to the API via POST method.
This is my main function: ProcessProducts which receives a Pandas Dataframe which includes a product per row.
def ProcessProducts(products):
all_results = []
for _, product in products.iterrows():
results = GetProductData(product)
if results:
all_results.extend(results)
return all_results
def GetAuthorizationToken():
payload = {
'version': api_version,
'client_id': api_client_id,
'client_secret': api_client_secret
}
request_url = '%s%s' % (api_host, '/api/authorize')
r = requests.post(request_url, data=payload)
if r.status_code != 200:
raise Exception('Failed to authorize: ' + r.text)
token_data = json.loads(r.text)
api_auth_token = token_data['auth_token']
api_auth_expires = token_data['auth_expires']
return {
"X-API-Version": api_version,
"Authorization": "token %s" % api_auth_token
}
Client function...
def GetProductData(self, product):
"""Gets Product information from API."""
url = '%s%s' % (self.api_url, _COMPANY_DATA)
request = json.dumps({'products': [product]})
form_data = {'request': request, 'start': 1, 'limit': 1000}
logging.info('Looking up: %s', url)
auth_headers = GetAuthorizationToken()
response = _SendApiRequest(url, auth_headers, form_data)
return _HandleResponse(response)
def _SendApiRequest(self, url, auth_headers, form_data):
session = requests.Session()
try:
response = session.post(
url,
timeout=(_CONNECT_TIMEOUT_SECONDS, _READ_TIMEOUT_SECONDS),
headers=auth_headers,
data=form_data,
verify=True) # Check for valid public/signed HTTPS certificate.
response.raise_for_status()
return response
except requests.exceptions.HTTPError as err:
logging.exception(err)
Questions:
API returns the code expiry field "auth_expires", Where may be the best way to check in code when token expires so I can request a new one?
Is there a better Pattern to call API, so I can control the QPS rate as well (Use RateLimiter). Right now I'm creating a Session per request, which may not be ideal.