In my application each administrator has app_metdata attribute that defines whether or not they have the admin role. I am using a rule (below) to add the admin scope if the user has this metadata.However, any API calls using the token returns an insufficient scope error. I have used jwt.io to verify that NO scopes are returned in my token. What do I have to do to ensure my authorized scopes are included in the JWT?
function (user, context, callback) {
var _ = require("lodash");
var req = context.request;
var scopes = (req.query && req.query.scope) || (req.body &&
req.body.scope);
// Normalize scopes into an array
scopes = (scopes && scopes.split(" ")) || '';
if (user.app_metadata !== undefined && user.app_metadata.roles !==
undefined && user.app_metadata.roles.indexOf('admin') >= 0) {
scopes.push("admin");
}
// Restrict the access token scopes according to the current user
//context.accessToken.scope = restrictScopes(user, scopes);
console.log('scopes',scopes.join(" "));
context.accessToken.scope = scopes.join(" ");
callback(null, user, context);
}
The rule looks correct. But the problem may be with loadash. I have used Authorized extension to add roles, permission, and group to users. It updates the user app_metadata field. To add the user's permissions in the scope, I used the following rule.
function (user, context, callback) {
if (context.request.query.audience === '[api identifier]' &&
context.clientID === '[client id]') {
var req = context.request;
var scopes = (req.query && req.query.scope) || (req.body && req.body.scope);
var permissions = user.permissions || [];
var requestedScopes = context.request.body.scope || context.request.query.scope;
var filteredScopes = requestedScopes.split(' ').filter( function(x) {
return x.indexOf(':') < 0;
});
Array.prototype.push.apply(filteredScopes, permissions);
console.log(filteredScopes.join(' '));
context.accessToken.scope = filteredScopes.join(' ');
}
callback(null, user, context);
}
https://auth0.com/docs/architecture-scenarios/spa-api/part-2#configure-the-authorization-extension
Make sure to restrict the permission to specific API and client. Otherwise, you might provide access to management API in the token mistakenly. If you are not using authorized extension, you can get the correct permission form the user object (user_metadata).
Alternatively, to add any specific API scope in the token for the API access, you can request the scope when you are calling /oauth/token or /authorize endpoint.
The /authorize endpoint is used for implicit grant , authorization code grant flow and authorization code grant with PKCE. The purpose of this call is to obtain consent from the user to invoke the API (specified in the audience field) and do certain things (specified in scope) on behalf of the user. Auth0 will authenticate the user and obtain consent, unless consent has been previously given. If you alter the value in scope, Auth0 will require consent to be given again. It should be executed in the browser
https://YOUR_AUTH0_DOMAIN/authorize?
audience=API_IDENTIFIER&
scope=openid%20email%20profile%20read:user&
response_type=token%20id_token&
client_id=YOUR_CLIENT_ID&
redirect_uri=https://YOUR_APP/callback&
state=123&
nonce=234
On the other hand, /oauth/token endpoint is used to implement clent credentials grant and resource owner password credentials grants. The following curl command ask for a token. It is using Resouce owner password credentials grant
curl --request POST \
--url 'https://YOUR_AUTH0_DOMAIN/oauth/token' \
--header 'content-type: application/json' \
--data '{"grant_type":"password","username": "user#example.com","password": "pwd","audience": "https://someapi.com/api", "scope": "read:sample", "client_id": "YOUR_CLIENT_ID", "client_secret": "YOUR_CLIENT_SECRET"}'
Note: You have to use Access token for API call.
Additionality, you can try adding admin role or permission as custom claim using rule in the access token or id_token. Based on the claim, you can restrict the access. For example,
function (user, context, callback) {
const namespace = 'https://myapi.com/';
context.idToken[namespace + 'user_metadata'] = user.user_metadata;
context.accessToken[namespace + 'user_metadata'] = user.user_metadata;
callback(null, user, context);
}
Related
I am using JWT token based authentication system. i.e djangorestframework-simplejwt in my backend
Now I am using reactj and axios as frontend:
After providing username and pass to the login api, I got access_token and refresh_token which I stored in the localstorage
Now I am trying to connect to an api using access_token.
I get Token invalid or expired
Example I am trying to change password using this api and provide access_token
const url = "dj-rest-auth/password/change/";
const auth = {
headers: {
Authorization: "Bearer " + localStorage.getItem("access_token"),
Accept: "application/json",
"Content-Type": "application/json",
},
};
const data = {
old_password: old_password,
new_password1: new_password1,
new_password2: new_password2,
};
const promise = axios.post(url, data, auth);
promise
.then((res) => {
console.log(res)
})
.catch((err) => {
if (err.response) {
console.log(`${err.response.status} :: ${err.response.statusText}`)
console.log(err.response.data)
}
})
I can do another api call using refresh_token to get access_token when i get an err.
But sometimes, the err can be due to network error or something else. Then even i try to get access_token using refresh_token, it will just get into a loop.
HOw to do this the right way
If you are using Django as the backend, I would suggest using dj-rest-auth for JWT token authentication. dj-rest-auth requires "djangorestframework-simplejwt" for token management.
It is recommended to store access token and refresh token in httponly cookie so that it is not accessed by javascript.
Add JWTtokenAuthentication as authentication classes in settings.py.
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'dj_rest_auth.jwt_auth.JWTCookieAuthentication'
]
}
Add the below configuration too in settings.py
REST_SESSION_LOGIN = False
SITE_ID=1
REST_USE_JWT = True
JWT_AUTH_COOKIE = 'access-token' #any name
JWT_AUTH_REFRESH_COOKIE = 'refresh_token' #any name
JWT_AUTH_SECURE = True
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_ALLOW_ALL = True
There is an open issue with dj-rest-auth, that requires the below code to be implemented in your back-end Github issue: https://github.com/iMerica/dj-rest-auth/issues/97. As workaround suggested, you have to create a file middleware.py and paste below code.
import json
from django.utils.deprecation import MiddlewareMixin
from yourapp.settings import JWT_AUTH_REFRESH_COOKIE # from settings.py
class MoveJWTRefreshCookieIntoTheBody(MiddlewareMixin):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
return response
def process_view(self, request, view_func, *view_args, **view_kwargs):
if request.path == '/token/refresh/' and JWT_AUTH_REFRESH_COOKIE in request.COOKIES:
if request.body != b'':
data = json.loads(request.body)
data['refresh'] = request.COOKIES[JWT_AUTH_REFRESH_COOKIE]
request._body = json.dumps(data).encode('utf-8')
else:
print("The incoming request body must be set to an empty object.")
return None
By now, your back-end will be successfully generating access token and refresh token. Even your back-end will be capable of refreshing access token using refresh token.
Front-End:
By default, access token and refresh tokens are stored in httponly cookie, so you don't need to worry about that part.
Axios can be used to make call to login-end point to get tokens. Make sure you use "withCredentials" and "Headers" in your request.
Response will be tokens, by default it will be stored in httponly cookie, since we are using dj-rest-auth. For all the consecutive requests, httponly cookie will be included, if tokens are valid, user will be provided access. IF token is expired, you need to make call to refresh endpoint to get new access token.
Since you are in development mode, you have to have same domain for both BE and FE, different ports.You can start django-server using below command and make sure your FE is also running in localhost
python manage.py runserver localhost:8080
dj-rest-auth : https://dj-rest-auth.readthedocs.io/en/latest/index.html
I am trying to secure a .NET 5.0 Web API with OAuth Client Credentials flow.
My Client is requesting a token from the IdentityServer4 instance and supplying it to the API. The API is then returning a 401 error when I access and endpoint. I notice the following header:
WWW-Authenticate header contains Bearer error=\"invalid_token\", error_description=\"The audience 'empty' is invalid\"
Which suggests my JWT does not contain the audience paramater.
My JWT request code looks like the following:
var tokenResponseType = await serverClient.RequestClientCredentialsTokenAsync(new
ClientCredentialsTokenRequest
{
Address = discoveryDocument.TokenEndpoint,
ClientId = "client_id",
ClientSecret = "client_secret",
Scope = "ApiOne",
});
The code to validate the Token is here:
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", config =>
{
config.Authority = "https://localhost:44335/";
config.Audience = "ApiOne";
config.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateActor = true,
ValidateLifetime = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true
};
config.RequireHttpsMetadata = false;
});
I believe the JWT token should contain the audience parameter. When I request the JWT I can't find a way to set the audience parameter.
I've used jwt.io to debug my JWT token and this confirms the audience value is not set. I expected setting the Scope on the request would do this.
What is lacking is the ApiScope and ApiResource configuration in IdentityServer.
First you need an ApiScope defined, like:
new ApiScope(name: "ApiOneScope",
displayName:"You can manage the ApiOne system.",
userClaims: new List<string>{ });
The ApiScope is a scope that the client can request access to.
then you need a ApiResource defined like:
new ApiResource()
{
Name = "ApiOne",
DisplayName = "Orders API Service",
Scopes = new List<string> { "ApiOneScope" },
};
The ApiResource is the actual Api, that end up in the audience claim when the clients requests the scope named ApiOneScope.
To complement this answer, I write a blog post that goes into more detail about this topic:
IdentityServer – IdentityResource vs. ApiResource vs. ApiScope
How to configure flask app with flask-jwt-extended for which we need something like below.
AccessToken/Bearer must sent as a Header (and not cookie)
RefreshToken must sent as httpOnlyCookie for /api/refreshtoken path only
How to set two different token one in header and one in cookie? We are able to set either both as cookie or both as a header.
Any help?
Thanks
Raxit
I wanted to do the same while building a React + Flask single page application after days of headache trying to understand authorization and authentication as I am a beginner.
Anyways, I managed to do it this way:
In Flask, config:
app.config['JWT_TOKEN_LOCATION'] = ['headers', 'cookies']
app.config['JWT_REFRESH_COOKIE_PATH'] = '/auth/refresh'
And what I return in my login function:
resp = jsonify({'access_token': access_token})
set_refresh_cookies(resp, refresh_token)
return resp, 200
And in my refresh function:
# Refresh access token
#app.route('/auth/refresh', methods=['POST'])
#jwt_refresh_token_required
def refresh():
user = get_jwt_identity()
resp = {
'access_token': create_access_token(
identity={
'username': user['username'],
'role': user['role']
},
expires_delta=timedelta(seconds=600),
user_claims=user['role']
)
}
return jsonify(resp), 200
And on the front side, I collect the JSON access_token and set it in memory and use withCredentials to send the refresh_token with my API calls.
axios.defaults.withCredentials = true;
axios.defaults.headers.common['Authorization'] = `Bearer ${access_token}`;
more precisely:
.then(({ data: { access_token } }) => {
axiosHttp.defaults.headers.common['Authorization'] = `Bearer ${access_token}`;
return jwt_decode(access_token);
})
then I use the data from my decoded access_token in a React Context Component to authenticate access to pages depending on roles.
logout is simply setting to null my context and calling the api to unset the refresh cookie
#app.route('/auth/logout', methods=['DELETE'])
#jwt_required
def logout():
resp = jsonify({"msg": "Successfully logged out"})
unset_jwt_cookies(resp)
return resp, 200
it's quite simple in the end but it took me quite a while to figure out!
So I have a code that logs a user in and then should get a list of the user's Pages in which he is an admin of.
FB.login(function (r) {
if (r.authResponse) {
FB.api("/me/accounts", "GET", { access_token: r.authResponse.accessToken }, function (response) {
..//
});
} else {
// not auth / cancelled the login!
}
}, { scope: "manage_pages, publish_pages, publish_actions" });
The problem I think is the user access token generated does not have manage_pages permission even though I asked for it during log in. I confirmed this by getting the user access token generated after logging in and then using Facebook's Access Token Debugger. How do I get a user access token with manage pages permission?
I am a part of a secret group. I want to get all of the posts and their metadata. I use the following code:
import facebook
if __name__ == '__main__':
APP_SECRET = ""
APP_ID = ""
PAGE_ID = "" ## Page ID of the secret group
access_token = facebook.get_app_access_token(APP_ID, APP_SECRET)
graph = facebook.GraphAPI(access_token)
resp = graph.get_object('me/accounts')
page_access_token = None
for page in resp['data']:
if page['id'] == PAGE_ID:
page_access_token = page['access_token']
graph = facebook.GraphAPI(page_access_token)
but I get this error:
facebook.GraphAPIError: An active access token must be used to query information about the current user.
on line resp = graph.get_object('me/accounts').
Where am I going wrong?
The error message means that you did not authorize the user. How to do that: https://developers.facebook.com/docs/facebook-login/
/me/accounts is the endpoint to get access to pages, for groups you need the user_managed_groups permission and the /me/groups endpoint. You need to use an active User Token for that, of course.
More information: https://developers.facebook.com/docs/graph-api/reference/v2.4/user/groups