I'm developing some SpringBoot microservices that exposes REST through WSO2 APIM.
Microservice itself does not implement any kind of authentication or authorization mecanism, it is delegated to APIM.
If I set API to use Password Grant as described here, front end application can authenticate and generate JWT token.
The problem now is that I can't fetch user roles from JWT payload because it is not being added by APIM. This information is important because front-end render menus and buttons based on user roles.
The user I'm passing when generate token does have some roles as you can see bellow:
But generated JWT token does not include any information about roles. Here is a sample token:
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5UZG1aak00WkRrM05qWTBZemM1TW1abU9EZ3dNVEUzTVdZd05ERTVNV1JsWkRnNE56YzRaQT09In0.eyJodHRwOlwvXC93c28yLm9yZ1wvY2xhaW1zXC9hcHBsaWNhdGlvbnRpZXIiOiJVbmxpbWl0ZWQiLCJodHRwOlwvXC93c28yLm9yZ1wvY2xhaW1zXC92ZXJzaW9uIjoidjEiLCJodHRwOlwvXC93c28yLm9yZ1wvY2xhaW1zXC9rZXl0eXBlIjoiUFJPRFVDVElPTiIsImlzcyI6IndzbzIub3JnXC9wcm9kdWN0c1wvYW0iLCJodHRwOlwvXC93c28yLm9yZ1wvY2xhaW1zXC9hcHBsaWNhdGlvbm5hbWUiOiJDYWRhc3RybyBkZSBDbGllbnRlcyIsImtleXR5cGUiOiJTQU5EQk9YIiwiaHR0cDpcL1wvd3NvMi5vcmdcL2NsYWltc1wvZW5kdXNlciI6ImVtaWxpb0BjYXJib24uc3VwZXIiLCJodHRwOlwvXC93c28yLm9yZ1wvY2xhaW1zXC9lbmR1c2VyVGVuYW50SWQiOiItMTIzNCIsImh0dHA6XC9cL3dzbzIub3JnXC9jbGFpbXNcL3N1YnNjcmliZXIiOiJhZG1pbiIsImh0dHA6XC9cL3dzbzIub3JnXC9jbGFpbXNcL3RpZXIiOiJVbmxpbWl0ZWQiLCJzY29wZSI6ImRlZmF1bHQiLCJleHAiOiIxNTk5NTYyOTQ4MDI4IiwiaHR0cDpcL1wvd3NvMi5vcmdcL2NsYWltc1wvYXBwbGljYXRpb25pZCI6IjIiLCJodHRwOlwvXC93c28yLm9yZ1wvY2xhaW1zXC91c2VydHlwZSI6IkFwcGxpY2F0aW9uX1VzZXIiLCJjb25zdW1lcktleSI6IktJaTdnUk1RYmg1OWZGbmpVOFhNbnhGcm9pNGEiLCJodHRwOlwvXC93c28yLm9yZ1wvY2xhaW1zXC9hcGljb250ZXh0IjoiXC9ia25nXC92MSJ9.km4w2V7dGmoGl8f4_ZqKHvdofAPLOOw__GPjWKrpjYelbi7IjDIpRODEZNn8hE1krRdDTSjKRviJ-NBvXtTXIiLdfPh1p-zNtX26vrS77ZcSZ2WsQA7Ku21YMqcm6cyZvEhZ99qfTxOtbJfkwt6Yt8itkyr-aqk83pNp85LTnwtNboib9VOOvh37zNEJUImzKw4WvENp4SGLuHO978FriHyHPN9vibzPjpItW5DOXTFNdN4rP6RK_vcOH6hpuZHwivJpTHxf9qMB3Gd2yTig-Hkr-sZGbx89pQf8kqtCLWbhRG5jOtcEJNf2CSNLB0Glg_e4F6LfhVD5JUCz15jdlg
When I extract it in https://jwt.io/ I get following payload:
{
"http://wso2.org/claims/applicationtier": "Unlimited",
"http://wso2.org/claims/version": "v1",
"http://wso2.org/claims/keytype": "PRODUCTION",
"iss": "wso2.org/products/am",
"http://wso2.org/claims/applicationname": "Cadastro de Clientes",
"keytype": "SANDBOX",
"http://wso2.org/claims/enduser": "emilio#carbon.super",
"http://wso2.org/claims/enduserTenantId": "-1234",
"http://wso2.org/claims/subscriber": "admin",
"http://wso2.org/claims/tier": "Unlimited",
"scope": "default",
"exp": "1599562948028",
"http://wso2.org/claims/applicationid": "2",
"http://wso2.org/claims/usertype": "Application_User",
"consumerKey": "KIi7gRMQbh59fFnjU8XMnxFroi4a",
"http://wso2.org/claims/apicontext": "/bkng/v1"
}
How do I add user roles to JWT payload? Do I need to implement a custom generator as described here?
Thanks in advance!
Easiest way to get role claim included in the auth JWT is to add a claim mapping in service provider level and request the token with openid scopes. To do this try below steps.
Log in to management console https://<host>:<port>/carbon
List service providers in the left menu
Go to edit on the required service provider (Each application in the developer portal has a mapping service provider)
Add a claim mapping to role claim as below
Send the token request with the scope=openid parameter
curl -k -X POST https://localhost:8243/token -d "grant_type=password&username=<Username>&password=<Password>&scope=openid" -H "Authorization: Basic <Credentials>"
Response access token will contain roles in this format
{
"sub": "admin#carbon.super",
"iss": "https://localhost:9443/oauth2/token",
"groups": [
"Internal/subscriber",
"Internal/creator",
"Application/apim_devportal",
"Application/admin_NewApp_PRODUCTION",
"Internal/publisher",
"Internal/everyone",
"Internal/analytics",
],
...
}
Related
I am using Oauth2+JWT+Spring security in one of my project. When i hit /outh/token using username/password i am receiving access token in the response with other details like
{
"access_token": <token>,
"token_type": <type>,
"refresh_token": <refresh-token>,
"expires_in":<secs>,
"scope": <scope>,
"jti": <value>
}
Is it possible to customize this response like
{
data: {
"access_token": <token>,
"token_type": <type>,
"refresh_token": <refresh-token>,
"expires_in":<secs>,
"scope": <scope>,
"jti": <value>
},
details:{
//extra information
}
}
I'm confused, don't know how to ask more clearly: "what is the additional data you want to send?". Just give an explicit sample.
I'll answer guessing that this data is related to the authorized entity (the user when answering within authorization-code flow and the client within client-credentials flow): non standard data should be set as private-claims, inside the JWT itself and on token introspection endpoint.
How to add such private claims depends on your authorization-server. For spring-security one, this is done with an OAuth2TokenCustomizer. For Keycloak, you have to provide a "mapper", etc.
The client which received a payload like the one you show, will only send the access-token when issuing requests to resource-server(s). It is important that this token holds all of the data needed for resources access decisions. Some claims are specified by OAuth2, OpenID defines some more standard claims, and you are free to put whatever additional data in private claims (within the limits of the maximum token size).
Also, if your client needs to read claims, it should use ID token, not access-token (request openid scope when initiating OAuth2 flow). Access-token should be interpreted by resource-server only.
Im trying to access an Outlook email using Oauth , Already set this permissions on Azure App:
So, using Oauth 2.0 client credentials grant I obtain the access Token:
But, when I try to authenticate with that token it fails, so I read that it was necessary to generate a second token call using that first one as a parameter and using imap and smtp scopes concatenated, and I tried like in the examples:
And as you see, I get this suffix error :
{
"error": "invalid_scope",
"error_description": "AADSTS1002012: The provided value for scope https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send
is not valid. Client credential flows must have a
scope value with /.default suffixed to the resource identifier (application ID URI).\r\nTrace ID: 41d2086b-f83e-41d8-b405-115249b27901\r\nCorrelation ID: 6afef855-3fc9-4259-864b-4ec8e293e9d0\r\nTimestamp: 2022-04-29 00:02:06Z",
"error_codes": [
1002012
],
"timestamp": "2022-04-29 00:02:06Z",
"trace_id": "41d2086b-f83e-41d8-b405-115249b27901",
"correlation_id": "6afef855-3fc9-4259-864b-4ec8e293e9d0"
}
Any idea what am I doing wrong ?, why the first token its not enough or how do I get the access one in the second call ?
Thanks in advance !!
I am using Keycloak as Authorization Server and setting up SPA (Public Client) that will be connecting to a REST service (Bearer-Only client / Resource Server).
As far as I understand, scopes are group of claims and claims are information about the subject (authenticated user). Scopes are passed at authentication step to the authorization server so that it will prompt the user to permit/deny access to those claims (user data / actions). In the case of third party client trying to access data from for example Facebook, scopes are permissions given by the user to the client to access user's data from Facebook. In case of Slack scope are used to allow / deny certain permissions for the third party client to perform operations on behalf of the user.
In my case where the resource server does not hold any user information but OAuth is used for authentication / authorization and will use the access token to get users group membership and possibly role to allow/deny access to the data on the resource server.
Correct me if I am wrong but scopes are used more for the user to allow third party client to access his/her information from OIDC provider, possibly get user information from the ID Token. On the other hand Roles / Groups that is set in the access token would be used to authorize the user to get data from the the resource server
For example here is the
Access Token:
{
"exp": 1612294865,
"iat": 1612294565,
"auth_time": 1612294524,
"jti": "e6c12a30-1b54-410a-81ba-ffe947dcebc9",
"iss": "http://localhost:8080/auth/realms/demo",
"aud": "rest-service",
"sub": "e49f2e27-4b47-492e-a714-2497d4f669f8",
"typ": "Bearer",
"azp": "js-console",
"nonce": "58faa6b8-a628-4b5a-8f0a-b33643812ef2",
"session_state": "2470b7b4-1587-43d5-8747-911b01398c3d",
"acr": "0",
"allowed-origins": [
"http://localhost:8000"
],
"resource_access": {
"rest-service": {
"roles": [
"user"
]
}
},
"scope": "openid email profile",
"email_verified": false,
"preferred_username": "test",
"group": [
"/my-group"
]
}
ID Token
{
"exp": 1612294865,
"iat": 1612294565,
"auth_time": 1612294524,
"jti": "c3242eb4-9047-4913-9ac6-be3b4e2b5745",
"iss": "http://localhost:8080/auth/realms/demo",
"aud": "js-console",
"sub": "e49f2e27-4b47-492e-a714-2497d4f669f8",
"typ": "ID",
"azp": "js-console",
"nonce": "58faa6b8-a628-4b5a-8f0a-b33643812ef2",
"session_state": "2470b7b4-1587-43d5-8747-911b01398c3d",
"at_hash": "5hOJUzWD9H2DOuYCEnc6fQ",
"acr": "0",
"email_verified": false,
"preferred_username": "test"
}
The audience(aud) for the ID token is set to the Client (js-console) and returns just some user information and really only needs openid scope. The access token aud is set to the rest service that will consume the access token and retrieve the roles and group information.
This is a different usage of OAuth which in this case the user doesn't need to consent to anything but will authenticate and use the access token to get information based on the group membership.
Is my understanding is correct?
Thanks!
In the manifest of my application registration I've configured to retrieve the given_name and family_name claims (through the UI, the resulting manifest looks like this):
"idToken": [
{
"name": "family_name",
"source": "user",
"essential": false,
"additionalProperties": []
},
{
"name": "given_name",
"source": "user",
"essential": false,
"additionalProperties": []
}
],
During the redirect I add the profile scope along with the given_name and family_name scopes, which results in the following error.
Message contains error: 'invalid_client', error_description: 'AADSTS650053: The application 'REDACTED' asked for scope 'given_name' that doesn't exist on the resource '00000003-0000-0000-c000-000000000000'. Contact the app vendor.
Any ideas? As I understand that is what is required to configure these optional claims on the v2.0 endpoint as described here: https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-optional-claims#v20-specific-optional-claims-set
You should only use the profile 'scope', which should result in you receiving the given_name and family_name 'claims'. That's standard behaviour for an Authorization Server, which will then either:
Return the name details directly in the id token
Or allow you to send an access token to the user info endpoint to get the name details
However, Azure v2 is very Microsoft specific, and user info lookup can be painful and involve sending a separate type of token to the Graph user info endpoint. Hopefully you won't have to deal with that and you will get the name details directly in the id token.
I had a scenario where my API (which only received an access token) needed to get user info, and I solved it via steps 14 - 18 of this write up, but it's a convoluted solution.
Once you configure optional claims for your application through the UI or application manifest. you need to provide profile Delegated permissions for the application.
I am using Laravel-php, I have following code :
$client = new Google_Client();
$client->setClientId(env('GOOGLE_ID'));
$client->setClientSecret(env('GOOGLE_SECRET'));
//$client->setRedirectUri($redirect_uri);
$client->addScope("https://www.googleapis.com/auth/youtube.force-ssl");
$client->addScope("https://www.googleapis.com/auth/youtube");
$client->addScope("https://www.googleapis.com/auth/youtube.readonly");
$client->addScope("https://www.googleapis.com/auth/youtubepartner");
$youtube = new \Google_Service_YouTube($client);
$searchResponse = $youtube->channels->listChannels('snippet', array('mine' => true));
//$subscriptions = Curl::to('https://www.googleapis.com/youtube/v3/subscriptions')->withData(['part' => 'snippet', 'mine' => 'true'])->get();
echo "<pre>";
print_r($searchResponse);
Above code gives me following error :
Google_Service_Exception in REST.php line 118:
{
"error": {
"errors": [
{
"domain": "usageLimits",
"reason": "dailyLimitExceededUnreg",
"message": "Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup.",
"extendedHelp": "https://code.google.com/apis/console"
}],
"code": 403,
"message": "Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup."
}
}
I have tried to use CURL call also but that also gives me same error, any suggestions will save my day
What am I missing in code ?
Your error means that you haven't set up a Google APIs console project. The resource you are accessing requires OAuth authorization. You need to obtain authorization credentials in the Google Developers Console to be able to use OAuth 2.0 authorization.
Open the Credentials page.
The API supports API keys and OAuth 2.0 credentials. In your case, use OAuth 2.0 for your project:
OAuth 2.0: Your application must send an OAuth 2.0 token with any request that accesses private user data. Your application sends a client ID and, possibly, a client secret to obtain a token. You can generate OAuth 2.0 credentials for web applications, service accounts, or installed applications.
See the Creating OAuth 2.0 credentials section for more information.
You may also check this related thread: list user subscriptions to all youtube channels after getting access token
First thing is that it needs to be an authenticated call.
So you need to get the person to "Authenticate" through the Oauth2 and collect the token.
Then with the token send this call
https://www.googleapis.com/youtube/v3/subscriptions?part=id,snippet,contentDetails&maxResults=50&channelId='.$channelId.'&access_token='.$access_token
Then you can access the JSON response and collect them.