NPE in Keycloak internal token-exchange - nullpointerexception

I'm trying to achieve an internal token exchange in Keycloak 17.0.1, however, the server returns an unknown error (NullPointerException).
My scenario is: I have three microservices, A, B, and C. A calls B, which is an intermediate service that needs to call service C. So, I don't want to propagate the original token (A) to call (C). Instead, I want to exchange the token, so B makes a token-exchange request to Keycloak to get a new token and then calls service C.
What I have done:
I have a client "original" who has his own client-id/client-secret
I created another client "target" and configured the policy for token exchange, assuming the "original" client in that policy.
And finally the cURL call:
curl -L -X POST 'http://localhost:8080/realms/myrealm/protocol/openid-connect/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=target' \
--data-urlencode 'client_secret=<< TARGET SECRET >>' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
--data-urlencode 'subject_token=<< ORIGINAL CLIENT TOKEN >>' \
--data-urlencode 'requested_token_type=urn:ietf:params:oauth:token-type:refresh_token' \
--data-urlencode 'audience=original'
Response:
2022-04-19 16:05:16,154 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (executor-thread-37) Uncaught server error: java.lang.NullPointerException
at org.keycloak.protocol.oidc.TokenManager.attachAuthenticationSession(TokenManager.java:539)
at org.keycloak.protocol.oidc.DefaultTokenExchangeProvider.exchangeClientToOIDCClient(DefaultTokenExchangeProvider.java:336)
at org.keycloak.protocol.oidc.DefaultTokenExchangeProvider.exchangeClientToClient(DefaultTokenExchangeProvider.java:315)
at org.keycloak.protocol.oidc.DefaultTokenExchangeProvider.tokenExchange(DefaultTokenExchangeProvider.java:233)
at org.keycloak.protocol.oidc.DefaultTokenExchangeProvider.exchange(DefaultTokenExchangeProvider.java:123)
at org.keycloak.protocol.oidc.endpoints.TokenEndpoint.tokenExchange(TokenEndpoint.java:789)
at org.keycloak.protocol.oidc.endpoints.TokenEndpoint.processGrantRequest(TokenEndpoint.java:204)
at jdk.internal.reflect.GeneratedMethodAccessor344.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:170)
at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:130)
at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:660)
at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:524)
at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:474)
at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:476)
at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:434)
at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:192)
at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:152)
Am I missing something?
UPDATE
The only way I've managed it to work was to "force" a session to be created in Keycloak by using a "password" grant type in the request of client A. So, I created a user foo and got a token in this way:
POST http://localhost:{{keycloak_port}}/realms/{{keycloak_realm}}/protocol/openid-connect/token
Authorization: Basic original:12345
Content-Type: application/x-www-form-urlencoded
grant_type=password
&username=foo
&password=bar
This way, a session was created for the client original and the token exchange request for the target client did work.
I'm wondering if it is a correct approach, though.

client configuration
As I'm using client_credentials OAuth2.0 flow, I had to enable the "Use Refresh Tokens For Client Credentials Grant" in Keycloak (clients/settings/OpenID Connect Compatibility Modes) and then toggle the option mentioned earlier.
Although OAuth2.0 states that refresh_tokens should not be used in this flow, I could not find another solution to this.
See attached image for more details.

Related

Cognito Service Authentication returns valid JWT but subsequent JWT validations fail

I have a lambda that accepts a username and password and returns a JWT. The code (seen below) basically uses cognito adminInitiateAuth() to use the username and password to get the JWT.
let USER_POOL_CLIENT_ID = "6adc4ziG7GCzYmMwhWWVnJySbP";
let USER_POOL_ID = "us-east-1_upxvqiJUP";
AWS.config.update({region: 'us-east-1'});
const cognito = new AWS.CognitoIdentityServiceProvider();
let params = {
AuthFlow: "ADMIN_NO_SRP_AUTH",
ClientId: USER_POOL_CLIENT_ID,
UserPoolId: USER_POOL_ID,
AuthParameters: {
USERNAME: event.username,
PASSWORD: event.password
}
};
//console.log(`${JSON.stringify(params)}`);
const jwtContainer = await cognito.adminInitiateAuth(params).promise();
console.log(`jwtContainer = ${JSON.stringify(jwtContainer)}`);
This seems to work and, in fact, does return a valid JWT with a kid decoded by jwt.io. Whenever I attempt to use the JWT on a different endpoint that is JWT protected (as seen below) I get an error:
curl -v https://myapi.execute-api.us-east-1.amazonaws.com/reset-service-auth-password \
-H 'Accept-Language: en-US,en;q=0.9' \
-X POST \
-d '{"password": "xyz123","confirmPassword": "22221"}' \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Origin: http://localhost:8080" \
-H 'Authorization: Bearer jwtdataxxxxxxxxxxxxxxxx'
But when I execute the above I get this error:
www-authenticate: Bearer scope="aws.cognito.signin.user.admin" error="invalid_token" error_description="unable to find an appropriate key in the JWKS. Ensure a "kid" is provided in the JWT, and that your JWKS has a supported cert available for this ID"
I checked my JWKS and the kid in the JWT is in fact in the JWKS. I am not sure what to do next or what is really wrong.
My authorizer expects the scope aws.cognito.signin.user.admin and my client does not require a secret. Please ask any clarification that you might need to help me with this issue.
I've had the same issue today. I hope you figured it out since then but, just in case, and for others...
I followed the same investigation steps that you did. I was convinced there was something wrong with AWS. There's not.
In my case, the token was generated against our production pool, but a development endpoint leaked in the config, and we were trying to authenticate on a wrong endpoint.
So my guess is that you got something similar: recheck your URLs, there's probably a mismatch between your pool ID and the API you request.

Keycloak - 500 Internal Server Error- when validating a token generated by a confidential client

In the Keycloak server, we created a client that has an Access Type of confidential.
By calling /protocol/openid-connect/token endpoint with the client_id and the client_secret, we got the access_token, which was supposed to authenticate the application (client) in question in the follow-up requests.
Even though the token is valid (which I double-checked by calling the introspect endpoint), I am getting a 500 Error from the server for any request that has this access_token
So in short:
Calling this endpoint <base_url>/auth/realms/<realm>/protocol/openid-connect/token gives an access_token
Provided data: client_id,client_secret, grant_type: "client_credentials"
Calling <base_url>/auth/realms/<realm>/protocol/openid-connect/userinfo gives 500Internal Server Error
{
"error": "unknown_error"
}
Any insights on what the issue could be?
This should be fixed in Keycloak 13.0.0.
See this commit: https://github.com/keycloak/keycloak/commit/056b52fbbe5af06aab957d37405215f1f4ed6ecd
It is not quite clear from the question how you are sending the token to the userInfo endpoint, Provided that the access token you have received is valid make sure that you are sending the token in the request header. Try the below curl command:-
curl
-X GET
-H "Authorization: Bearer <Access Token>"
-H "Content-type: application/json"
http://{hostname}/auth/realms/{realm_name}/protocol/openid-connect/userinfo

Watson speech to text authentication

I am trying to get Transcribe from Microphone working on my server as a starting point.
The code is straightforward but I am having trouble with the token.
on IBMcloud I created a IAM-Service id with Access Policies
Viewer, Reader 14 Speech to Text service in all resource groups
created an apikey from that
created the token file
curl -k -X POST --output token \
--header "Content-Type: application/x-www-form-urlencoded" \
--header "Accept: application/json" \
--data-urlencode "grant_type=urn:ibm:params:oauth:grant-type:apikey" \
--data-urlencode "apikey={apikey}" \
"https://iam.cloud.ibm.com/identity/token"
the response has 'access_token' but the javascript SDK 0.38.1 looks for 'accessToken'
when I start microphone I get a socket error 'wss://stream.watsonplatform.net/speech-to-text/api/v1/recognize"
I checked token expiration.
I confirmed it is an auth problem:
curl -X GET "https://stream.watsonplatform.net/speech-to-text/api/v1/models?access_token="{accessToken}"
responds "unauthorized"
I have researched and am unsure what to do next. My best guess is I am generating the token improperly.
I would leave the token generation to the code. All the SDKs have an IAMAuthenticator component. The full documentation for Node.js is here. It has a very simple example where you pass in the API key:
import { IamAuthenticator } from 'ibm-cloud-sdk-core';
const authenticator = new IamAuthenticator({
apikey: '{apikey}',
});
Thereafter, you instantiate the service, e.g., STT.

Waste Management API - Authorization: Bearer problem

Im reading this documentation:
https://api.wm.com/howtotest/#make-an-api-call
And I want to make this test request in the postman:
curl -i https://apitest.wm.com/v1/helloworld \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJhbnlvbmVAYW55LmNvbSIsInN1YiI6Ildhc3RlIG1hbmFnZW1lbnQgIHRlYW0iLCJqdGkiOiIwQkQyRTVDQkM2RDE2Mzc0RkNFQSIsInNjb3BlIjpbInNlbGYiLCJoZWxsb3dvcmxkIl0sImlhdCI6MTQ5MDg5ODk1NSwiZXhwIjoxNTIyNDM0OTU1fQ.O2k-senypXFZQwW4Ln3mBg60qzOSo-diPQWVfir3m6Q" \
-H "ClientId: 0BD2E5CBC6D16374FCEA" \
-H "Request-Tracking-Id: 12132"
But i recived response like this:
{
"message": "'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJhbnlvbmVAYW55LmNvbSIsInN1YiI6Ildhc3RlIG1hbmFnZW1lbnQgIHRlYW0iLCJqdGkiOiIwQkQyRTVDQkM2RDE2Mzc0RkNFQSIsInNjb3BlIjpbInNlbGYiLCJoZWxsb3dvcmxkIl0sImlhdCI6MTQ5MDg5ODk1NSwiZXhwIjoxNTIyNDM0OTU1fQ.O2k-senypXFZQwW4Ln3mBg60qzOSo-diPQWVfir3m6Q' not a valid key=value pair (missing equal-sign) in Authorization header: 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJhbnlvbmVAYW55LmNvbSIsInN1YiI6Ildhc3RlIG1hbmFnZW1lbnQgIHRlYW0iLCJqdGkiOiIwQkQyRTVDQkM2RDE2Mzc0RkNFQSIsInNjb3BlIjpbInNlbGYiLCJoZWxsb3dvcmxkIl0sImlhdCI6MTQ5MDg5ODk1NSwiZXhwIjoxNTIyNDM0OTU1fQ.O2k-senypXFZQwW4Ln3mBg60qzOSo-diPQWVfir3m6Q'."
}
This is the example of their official documentation, how it can be it doesnt works, or I didnt do something good?
I think that the error message is potentially misleading. If you are using the test example you need to request a clientid and token from WM per the documentation.
From the documentation:
With a valid access token and clientId, you are ready to make requests to Waste Management API.

Google Sheet Update Api : Getting error [Request is missing required authentication credential]

I am calling Google Sheet Update Request in Postman with following parameters:
Url:https://sheets.googleapis.com/v4/spreadsheets/SpreadSheetId/values/SheetName!A1:D5
Request Type : PUT
Headers : Authorization - MyAccessToken
Request Body :
Getting Response :
Note : For Updating into Spreadsheet, I follow these steps :
Create an application on Google Api Console using this link and then move to Credentials section. Click Create Credentials, then OAuth Client ID, Create Application type as Web Application. For now leave the Restrictions Section. Then Create. Now Application is created.
Add some project Name in the OAuth Consent Screen Tab
Now, Authorize the API using this link. In the right corner, open the Settings Icon. Select Use your own OAuth Credentials as true. Add the Client Id and Client secret from the Project on Google Api Console. Select the Scope as https://www.googleapis.com/auth/spreadsheets under the Google Sheet Api v4 in the dropdown, then Authorize the API.
If Get redirect_mismatch_uri. then Copy the redirect URI till the oathPlayground or before the prompt parameter and paste it into the Authorized Redirect URI under the restrictions Section of project in Google Api Console. Then Access Token will be generated,
Get Access Token. Copy this access token in the request which I defined earlier.
But Getting error. I am not able to figure out the problem.
Thanks in Advance..
Since you already have a refresh token, you can retrieve access token using refresh token. As a sample, it shows a method of how to retrieve access token using refresh token by curl. The access token retrieved by refresh token has the expiration time. Please confirm it.
You can see the detail infomation here. https://developers.google.com/identity/protocols/OAuth2WebServer
Curl command :
curl -L \
--data "refresh_token=### refresh token ###" \
--data "client_id=### client id ###" \
--data "client_secret=### client secret ###" \
--data "grant_type=refresh_token" \
https://www.googleapis.com/oauth2/v4/token
Result :
{
"access_token": "### access token ###",
"token_type": "Bearer",
"expires_in": 3600
}
Below curl command is for retrieving the information of access token. If the expiration time of access token had been over, the response is {"error_description": "Invalid Value"}. If it's not, you can see a following response.
Retrieve information of access token :
curl -L \
--data "access_token=### access token ###" \
https://www.googleapis.com/oauth2/v2/tokeninfo
Result :
{
"issued_to": "#####",
"audience": "#####",
"scope": "#####",
"expires_in": 1234,
"access_type": "offline"
}
EDIT1
This sample code is for updating the range of sheet1!A1:C2 to [["test1","test2","test3"],["test4","test5","test6"]].
Sample code :
curl -L -X PUT \
-H "Authorization: Bearer ### access token ###" \
-H "Content-Type: application/json" \
-d '{"values":[["test1","test2","test3"],["test4","test5","test6"]]}' \
"https://sheets.googleapis.com/v4/spreadsheets/### spreadsheet ID ###/values/sheet1%21a1%3ac2?valueInputOption=USER_ENTERED"
Result :
{
"spreadsheetId": "### spreadsheet ID ###",
"updatedRange": "sheet1!A1:C2",
"updatedRows": 2,
"updatedColumns": 3,
"updatedCells": 6
}