I have been struggling with this problem for two weeks, Basically I have configured the auth0 settings with my Flask app which runs on local host.
So, I have the following two endpoints in my flask app:
A public endpoint which requires no authentication process:
#APP.route("/api/public")
#cross_origin(headers=["Content-Type", "Authorization"])
def public():
# No access token required to access this route
response = "Hello from a public endpoint! You don't need to be authenticated to see this."
return jsonify(message=response)
A private endpoint which requires authentication:
#APP.route("/api/private")
#cross_origin(headers=["Content-Type", "Authorization"])
#cross_origin(headers=["Access-Control-Allow-Origin", "http://localhost:3000"])
#requires_auth
def private():
# A valid access token is required to access this route
response = "Hello from a private endpoint! You need to be authenticated to see this."
return jsonify(message=response)
So here is the following scenario:
I try to log in with the auth0 url that redirects me to universal login page for my app. After successful login, it redirects me to the private end point and I get 401 Un-authorized error.
Whenever I make this request with valid token from the browser it throws 401 error.
With the same token, I call this endpoint using postman it works !!
The reason for that is this request from the browser is not including the Authorization header, unlike postman.
I really don't understand why the browser is not including the Authorization header.
Can someone explains ?
NOTE: At first it was working using browser without any problems but suddenly it appeared somehow.
Since the private endpoint requires authentication, whenever I try to access the private end point this function is called:
def get_token_auth_header():
"""Obtains the access token from the Authorization Header
"""
auth = request.headers.get("Authorization", None) # HERE IS THE PROBLEM OCCURRS
print("REQUEST HEADERS: \n", request.headers)
if not auth:
raise AuthError({"code": "authorization_header_missing",
"description":
"Authorization header is expected"}, 401)
parts = auth.split()
if parts[0].lower() != "bearer":
raise AuthError({"code": "invalid_header",
"description":
"Authorization header must start with"
" Bearer"}, 401)
elif len(parts) == 1:
raise AuthError({"code": "invalid_header",
"description": "Token not found"}, 401)
elif len(parts) > 2:
raise AuthError({"code": "invalid_header",
"description":
"Authorization header must be"
" Bearer token"}, 401)
token = parts[1]
return token
I have been struggling with this for almost two weeks, I tried everything.
I would really appreciate your help.
I'm copying here the same answer I provided in the community forum in case you still need it ;)
It seems you are missing the authlib configuration ;)
You can see here how to configure that and use it on your app
Related
I am trying to create an API for our organization using FastAPI. It has a KeyCloak server that is used for all authentication, and OpenID Connect and JWTs in the way that is considered best practice.
In the simplest case, someone else takes care of acquiring a valid JWT token so that FastAPI then can simply decode and read the user and permissions.
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
jwt_token = jwt.decode(token, key=env.keycloak_server_public_key, audience='myorg')
return jwt_token['preferred_username']
except jwt.exceptions.ExpiredSignatureError:
raise credentials_exception
Life is simple!
I do, however, want to try to let users explore the API using the Swagger page. I have created this function that lets users login using the UI:
#app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
login_request = requests.post(
"https://mygreatorg.com/auth/realms/master/protocol/openid-connect/token",
data={
"grant_type": "password",
"username": form_data.username,
"password": form_data.password,
"client_id": "fastapi-application",
},
)
raw_response = json.loads(login_request.content.decode('utf-8'))
raw_response['acquire_time'] = time.time()
TOKEN_CACHE[form_data.username] = raw_response
return {"access_token": raw_response['access_token'], "token_type": "bearer"}
This works fine. The auth header in Swagger is now the token, and it validates, for about a minute. The expire time for the tokens is set to a very short time. One is then expected to refresh them using the refresh_token provided in the raw_response payload.
I can very easily make another request to get a new valid access token given the refresh_token. But I am unable to get Swagger to change the token of the request in the UI. The only way I find is to log out and log in again, but users will be very annoyed if they only allow a minute without being kicked out.
One workaround would be to simply cache the token and ignore the expiration time and let the user be logged in for a while longer, but that defeats the purpose of the entire security setup and feels like a bad idea.
Any ideas on how to let the UI of FastAPI update the bearer token when it needs a refresh, without letting the user log in again?
This is far from an answer and I will likely delete this later. It is a only a placeholder for outlining my research into this question
USE CASE: Swagger UI need to auto refresh with updated JWT token without closing the UI.
Systems/Applications:
KeyCloak
FastApi
Swagger
OpenID Connect and JWTs
When I looking into this question I noted that the issue in this question was raised in the issues at both FastApi and Swagger.
Swagger
When looking through the code base of Swagger, I noted an authorization parameter named persistAuthorization. According to the documentation this parameter will maintain authorization data and this data will not be lost on browser close/refresh.
In the Swagger code base I see this item:
# source: /src/plugins/topbar/topbar.jsx
#
flushAuthData() {
const { persistAuthorization } = this.props.getConfigs()
if (persistAuthorization)
{
return
}
this.props.authActions.restoreAuthorization({
authorized: {}
})
}
The code above makes calls to /src/core/plugins/auth/actions.js.
In the Swagger pull requests there is a pending feature named configs.preserveAuthorization This scope of this feature:
Refreshing or closing/reopening the the page will preserve authorization if we have configs.preserveAuthorization set to true.
It's unclear based on the comments how the features preserveAuthorization and persistAuthorization are different.
FastApi
When I was looking at the FastApi code base, I noted a OAuthFlowsModel named refreshUrl. I looked through the FastApi document and didn't see this mentioned.
# source: /fastapi/security/oauth2.py
#
class OAuth2AuthorizationCodeBearer(OAuth2):
def __init__(
self,
authorizationUrl: str,
tokenUrl: str,
refreshUrl: Optional[str] = None,
scheme_name: Optional[str] = None,
scopes: Optional[Dict[str, str]] = None,
auto_error: bool = True,
):
if not scopes:
scopes = {}
flows = OAuthFlowsModel(
authorizationCode={
"authorizationUrl": authorizationUrl,
"tokenUrl": tokenUrl,
"refreshUrl": refreshUrl,
"scopes": scopes,
}
)
super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)
async def __call__(self, request: Request) -> Optional[str]:
authorization: str = request.headers.get("Authorization")
scheme, param = get_authorization_scheme_param(authorization)
if not authorization or scheme.lower() != "bearer":
if self.auto_error:
raise HTTPException(
status_code=HTTP_401_UNAUTHORIZED,
detail="Not authenticated",
headers={"WWW-Authenticate": "Bearer"},
)
else:
return None # pragma: nocover
return param
Looking through the issues for FastApi I noted one asking for OAuth2RerefreshRequestForm to be added. The scope of this issue is token refresh.
I also noted another issue refresh token with Keycloak and Fastapi
I apologize for not being able to provide a solid answer.
I have a Python/Bottle server application, a Seaweed media server, and an NGINX web server.
I need to check the client's permission before allowing shim to download a file.
So:
User requests to download a file.
Backend checks user permission. If granted, redirects it to the file location.
But the problem is that the Seaweed secured by JWT and an Authorization header must be set.
Can I set Authorization header and then redirect user?
Yes, you can set headers on redirects. Here's one way to do it in Bottle:
#route(...)
def handler():
# after you've authorized the user, set your auth header:
response.set_header('Your-auth-header', auth_token)
# perform the redirect
response.status = 302
response.body = ''
response.set_header('Location', file_location)
raise response
(That's off the top of my head; not tested code.)
I am trying to use test the API request by using tokens. I was able to extract the token but I struggle to find a way to use it.
This is how I get my token:
#pytest.mark.django_db
class TestUserAPI(APITestCase):
def setUp(self):
self.created_user = UserFactory()
User.objects.create_user(username='test', password='pass1234')
def test_authentification(self):
request = self.client.post('http://localhost:8000/api/v1/auth/',
{
"username": "test",
"password": "pass1234"
})
TestUserAPI.token = request.data["token"]
assert request.status_code == 200
and this is how I use it:
def test_profile(self):
request = self.client.get('http://localhost:8000/api/v1/profile/',
TokenAuthentication = 'token {}'.format(TestUserAPI.token))
assert request.status_code == status.HTTP_200_OK
It gives me 401 error. What is the correct way of using token?
The solution is simple and it's because of my inexperience with testing. The test_profile function doesn't register that a token has been asked in the test_authentification function. So I had to put both of them in the SetUp function for the token to be registered for every function in the class.
According to documentation correct header format is Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
According to official documentation the the token key should be Authentication and the token should be should be trailing Token and a white space.
In your code change token {} to Token {}.
If the problem persists try to print request headers in you view by printing request.META to check the key.
I am trying to authenticate user using FIWARE.
It returns a 404. Thus fails at Step 1 itself. What is the access token url ? Any other pointers to check
I have tried variations with 'oauth/access_token', 'oauth/token' 'oauth2/token' 'oauth2/access_token' . All of them dont seem to work.
My Code is Below:
import oauth2 as oauth
# OAuth secret into your project's settings.
consumer = oauth2.Consumer(settings.FIWARE_CLIENT_ID,settings.FIWARE_CLIENT_SECRET)
client = oauth2.Client(consumer)
access_token_url = 'https://account.lab.fiware.org/oauth2/access_token'
# This is the slightly different URL used to authenticate/authorize.
authenticate_url = 'https://account.lab.fiware.org/oauth2/authorize'
def fiware_login(request):
# Step 1. Get a request token from FIWARE.
resp, content = client.request(access_token_url, "GET")
print resp
if resp['status'] != '200':
print content
raise Exception("Invalid response from FIWARE.")
# Step 2. Redirect the user to the authentication URL.
url = "%s?access_token=%s" % (authenticate_url,
resp['access_token'])
return HttpResponseRedirect(url)
Correct endpoint is "/oauth2/token".
Maybe you should use POST method instead of GET.
For more information see https://github.com/ging/fi-ware-idm/wiki/Using-the-FI-LAB-instance
I have successfully inserted a moment with this procedure before and am trying to figure out if the API is currently broken or i'm making a mistake.
I have obtained a valid access_token with request_visible_actions=http://schema.org/AddAction as seen in the partial url below from the auth stage as well as plus.login scope:
https://accounts.google.com/o/oauth2/auth?access_type=offline&request_visible_actions=http%3A%2F%2Fschema.org%2FAddAction&
the request code:
import requests
import json
moment = {"type":"http://schema.org/AddAction",
"object": {"id": "object-id-1",
"type":"http://schema.org/AddAction",
"name": "The Google+ Platform",
"description": "A page that describes just how awesome Google+ is!",
"image": "https://developers.google.com/+/plugins/snippet/examples/thing.png"}}
url ='https://www.googleapis.com/plus/v1/people/me/moments/vault'
params = {'access_token': 'MY_VALID_ACCESS_TOKEN'}
r = requests.request("POST",url=url, params=params, json=moment, headers=headers)
print r.url
print r.json()
and the result:
https://www.googleapis.com/plus/v1/people/me/moments/vault?access_token=MY_ACCESS_TOKEN
{u'error': {u'code': 401,
u'message':
u'Unauthorized',
u'errors': [{u'domain': u'global', u'message': u'Unauthorized', u'reason': u'unauthorized'}]}}
I tried to verify with the tokeninfo endpoint that request_visible_actions are present, but it's not listed there.
confirmed granted scopes:
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/plus.login',
'https://www.googleapis.com/auth/plus.profile.emails.read',
'https://www.googleapis.com/auth/plus.moments.write',
'https://www.googleapis.com/auth/plus.me',
'https://www.googleapis.com/auth/plus.profile.agerange.read',
'https://www.googleapis.com/auth/plus.profile.language.read',
'https://www.googleapis.com/auth/plus.circles.members.read'
I am able to get moments/vault with the same token so i know it's valid.
Any ideas here?
The procedure shown in the question does in fact work as long as request_visible_actions=space separated list of app activity types was included when the user makes the first connection to your app and the app activity type you are trying to post was included. If you want to update the list or add the moment.insert permission after initial authentication, the user must first disconnect the app from their google+ settings and reconnect.
It seems this is a bug with google's implementation of request_visible_actions and an incremental authorization strategy.
request_visible_actions seems to only be accepted during initial authorization and disregarded after that. This makes incrementally adding this parameter impossible.
Further more, request_visible_actions is not available when calling the tokeninfo endpoint, which leads me to believe that request_visible_actions is somehow dissociated from token auth and merely an attribute assigned to the user's app connection on instantiation.
Posted to exising moments.instert 401 issue: