I'm currently implementing a node.js module for Keycloak which uses the keycloak auth utils internally. For fine-grained authorization it should be possible to retrieve the entitlements. This works like a charm for "clientId + clientSecret" JWTs.
Additionally it should be possible to retrieve those entitlements for signed JWTs. When upload the key to my keycloak instance and sign the JWT with the private Key, it validates successfully with the help of the server and the stored public key. So the signed JWT is definitely valid (of course I changed the client's authenticator).
The issue is:
When I pass the signed JWT to the entitlement endpoint I get the following error:
{
"error": "invalid_bearer_token",
"error_description": "Could not obtain bearer access_token from request."
}
The request:
curl -k -L -H 'Content-Type: application/json' -H 'Authorization: bearer <signed_jwt>' '<realm_url>/authz/entitlement/foobar'
Now the question:
Is it possible to retrieve entitlements for signed JWTs and if yes, how?
What's my fault?
Thanks!
The solution is to include the kid header while signing the token.
Related
I am integrating my API backend with DocuSign in order to send and retrieve envelopes. I am using the JWT Grant flow.
Authentication options
In the DocuSign development environment, I am able to retrieve an access token using the JWT flow and the Docusign C# SKD.
I need to then call the oauth/userinfo endpoint in order to retrieve the base_uri field to use for making calls to Docusign.
When I make a GET request to https://account-d.docusign.com/oauth/userinfo, including the access token in the Authorization header as Bearer <access_token>, I receive a 401 Unauthorized response, and this message in the body:
{
"error": "internal_server_error",
"reference_id": "<some GUID>"
}
I have tried this using curl, Postman and the C# SDK and all give the same result.
Curl syntax:
curl --header "Authorization: Bearer <access token>" https://account-d.docusign.com/oauth/userinfo
user-info endpoint documentation
JWT flow (step 4)
As far as I can see, I appear to be calling the API according to the documentation and I have set up the account with an RSA key pair which is required for system integrations (created within the Docusign admin portal).
Can anyone think of a reason this could be happening?
Since you're using the C# SDK as you mentioned, you can call this endpoint using the same SDK if you have a valid token.
https://developers.docusign.com/docs/esign-rest-api/sdk-tools/c-sharp/reference/
public OAuth.UserInfo GetUserInfo(string accessToken);
You can confirm that your token is valid by trying to use it for other API calls.
A token from the developer account should be useful to make this call in the developer account only. If you need this for production (typically reason to need the base_uri) then you have to call it with account.docusign.com not account-d.docusign.com.
I have now been able to get the base_uri from UserInfo endpoint using the RequestJWTUserToken method in the C# SDK. Using this token allows me to hit the REST API endpoint.
Both methods appear to hit the same oauth/token endpoint and use the same grant type, only RequestJWTUserToken includes the "sub" claim for the userId.
I am trying to get an access token from Azure Active Directory, where I have registered an app and uploaded a certificate (in Certificates and secrets blade). AD should use this certificate to sign the access token that it will send in the response. I would like to get the access token response in Postman.
Please suggest a general method or steps to setup postman and call AD.
Please see this sample.
POST /{tenant}/oauth2/v2.0/token HTTP/1.1 // Line breaks for clarity
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
scope=https%3A%2F%2Fgraph.microsoft.com%2F.default
&client_id=97e0a5b7-d745-40b6-94fe-5f77d35c6e05
&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer
&client_assertion=eyJhbGciOiJSUzI1NiIsIng1dCI6Imd4OHRHeXN5amNScUtqRlBuZDdSRnd2d1pJMCJ9.eyJ{a lot of characters here}M8U3bSUKKJDEg
&grant_type=client_credentials
I think all the parameters are clear except client_assertion, which is the aforementioned signed JWT token.
You need to generate the JWT Token by following certificate credentials.
And then use this client_assertion in Postman.
We are using Azure active directory Oauth code flow to authenticate the user.
We got the access_token, id_token and refresh_token using code(got it on redirect URL).
We are using id_token to authorization each request after successful authentication and we can verify JWT using the public key which we got from /discovery/v2.0/keys api.
Now, JWT will expire after 1 hour. so we need to refresh this id_token.
I am refreshing id_token using below cURL
curl --request POST \
--url https://login.microsoftonline.com/{tenant_id}/oauth2/token \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data 'client_id=client%20id&refresh_token=refesh%20token%20&grant_type=refresh_token&client_secret=client_secret&scope=openid
In the response of this API, we got the access_token, refresh_token, id_token, but if you observe id_token, it does not contain JWT signature (the third part of JWT), without signature we can not verify JWT
We can not find any document reference why id_token is not having a signature part.
Is there any other way to refresh id_token?
Is there any workaround if id_token can not be refreshed?
In some cases an Id token may be returned without a signature, especially if it is acquired through a back-channel with a client secret.
You already are getting the token through an encrypted channel, from the authority.
If someone was able to inject something there, they would be able to direct your app's requests for OpenID metadata as well, replacing the signing keys your app expects.
Thus the signature would not add much value here.
In addition, you won't send the Id token to any API as Id tokens should not be used for authorization.
That's what access tokens are for :)
The OpenID Connect spec has this to say: https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
If the ID Token is received via direct communication between the Client and the Token Endpoint (which it is in this flow), the TLS server validation MAY be used to validate the issuer in place of checking the token signature. The Client MUST validate the signature of all other ID Tokens according to JWS [JWS] using the algorithm specified in the JWT alg Header Parameter. The Client MUST use the keys provided by the Issuer.
But then, interestingly mentions this as well: https://openid.net/specs/openid-connect-core-1_0.html#IDToken
ID Tokens MUST be signed using JWS and optionally both signed and then encrypted using JWS and JWE respectively, thereby providing authentication, integrity, non-repudiation, and optionally, confidentiality, per Section 16.14. If the ID Token is encrypted, it MUST be signed then encrypted, with the result being a Nested JWT, as defined in [JWT]. ID Tokens MUST NOT use none as the alg value unless the Response Type used returns no ID Token from the Authorization Endpoint (such as when using the Authorization Code Flow) and the Client explicitly requested the use of none at Registration time.
So.. it could be that Azure AD is not compliant with spec?
EDIT: The v1 endpoint of Azure AD is not compliant in this regard. The newer v2 endpoint is fully OpenID Connect compliant.
User trying to access /hello url in the browser via a reverse-proxy.
"500 Error: Cannot exchange code for grant in bearer-only mode"
error is popped after successful login by the user on Keycloak login page on access to protected url.
The following code is used in the reverse proxy:
var Keycloak = require('keycloak-connect');
var session = require('express-session');
var memoryStore = new session.MemoryStore();
let keycloak = new Keycloak(memoryStore);
app = express();
app.use( keycloak.middleware() );
app.get( '/hello', keycloak.protect( 'realm:admin' ));
keycloak.json is:
{
"realm": "master",
"auth-server-url": "https://127.0.0.1/auth",
"resource": "test_ui",
"confidential-port": 0,
"credentials" : {
"password" : "d31c4718-12e9-407b-9bf2-cb72734a23f0"
}
}
client test_ui is confidential.
Adding bearer-only : true to keycloak.json results in access denied error instead of the above error.
What's wrong with the configuration?
First of all: your keycloak.json config and node.js keycloak.protect app code are unaligned:
keycloak.json describes "realm":"master"
But you are invoking keycloak.protection on your app's GET /hello resource for admin realm only: app.get( '/hello', keycloak.protect( 'realm:admin' ));
TRY CHANGING THE REALM TO MASTER
app.get( '/hello', keycloak.protect( 'realm:master' ));
YOU CAN TEST IF IT WORKS USING cURL
curl -i http://YOUR_APP_SERVER_HOST/hello -H "Authorization: Bearer YOUR_BEARER_TOKEN"
DON'T FORGET TO SET UP BEARER AUTHENTICATION ON KEYCLOAK
https://www.keycloak.org/docs/3.1/authorization_services/topics/enforcer/keycloak-enforcement-bearer.html
AND GET YOUR BEARER TOKEN FIRST
curl \
-d 'client_id=YOUR_KEYCLOAK_CLIENT' \
-d 'username=YOUR_USERNAME' \
-d 'password=YOUR_PASSWORD' \
-d 'grant_type=password' \
'https://YOUR_KEYCLOAK_SERVER_HOST/auth/realms/YOUR_REALM/protocol/openid-connect/token'
NOW, THE LITERATURE
What happens here is essentially:
On your first request: you are trying to access functionality that requires Bearer token authentication, but you are using grant exchange code instead. Hence the error 500 response. Are you authenticating first against your keycloak /auth authentication service endpoint using your user credentials in order to obtain a valid Bearer token for that realm? Or are you, on the contrary, mistakenly trying to use your user credentials to authenticate directly against your app's service GET /hello API resource endpoint instead?
Then, on your second request you try enabling Bearer only authentication, but:
Your client is not examining the provided response WWW-Authenticate header for determining the required authentication method and, most importantly:
Your client is not issuing a new GET /hello request providing a valid HTTP Authorization header with your obtained Bearer token valid for GET /hello's assigned realm. Therefore you simply get an HTTP authentication error response (Typically 401 Unauthorized HTTP response, although 403 Forbidden is sometimes used instead).
In short, you need to align the realm and auth scheme on client and app server and auth server config, so:
Your app server endpoint GET /hello will send a WWW-Authenticate HTTP header specifying the required user realm and authentication scheme.
Your client will then use the provided user credentials to authenticate on your keycloak authentication server and receive a Bearer token.
You must have configured the keycloak authentication server to support the required authentication scheme for that realm and the user must have permissions to operate on that realm. Check out the keycloak server administrative console for that purpose.
Finally, once this is done for that realm and authentication scheme, your client will be able to issue a GET /hello request to your app server containing the acquired HTTP Bearer token on the HTTP Authorization header. cURL example:
curl -i http://example.com/api/hello -H "Authorization: Bearer mytoken123"
For more info:
Bearer authentication flow is described on RFC 6750 OAuth 2.0 Bearer Token Usage as part of the OAuth 2.0 standard.
Keycloack documentation describes how to set up bearer token authentication on keycloak.json.
The described steps to protect your node.js application GET /hello resource (You might want to use a custom handler).
The docs on the predefined keycloak master realm (sort of an admin scope) and how to create a new realm, user scoped, for your app's users.
I am getting invalid signature while using jwt.io to validate my azure ad access token (will shift to scala code after the manual checking).
I am using curl to generate the access token:
curl -s -X POST https://login.microsoftonline.com/<tenant id>/oauth2/token -d grant_type=password -d username=$username -d password=$pass -d resource=$resID -d client_id=$id -d client_secret=$key
While it is giving me the access token, the response doesnt contain the "Id_token". Not sure why.
I am wrapping the public key from https://login.microsoftonline.com/common/discovery/keys with BEGIN and END certificate. (as mentioned in https://nicksnettravels.builttoroam.com/post/2017/01/24/Verifying-Azure-Active-Directory-JWT-Tokens.aspx)
I am not sure what else is missing.
The decoded header is as follows:
{
"typ": "JWT",
"alg": "RS256",
"x5t": "9FXDpbfMFT2SvQuXh846YTwEIBw",
"kid": "9FXDpbfMFT2SvQuXh846YTwEIBw"
}
Warning: You are invoking a flow which is unsupported, and will be removed in the near future.
The Resource Owner Password Credentials Grant flow (grant_type=password) is not supported in Azure AD with confidential clients (i.e. web app/web API, where there exists a client_secret). If you are confident your scenario requires the Resource Owner flow (very few scenarios actually warrant the risks introduced by this flow), then you should be invoking it with a client_id registered for a native client app (a public client). Alternatively, you should be invoking an interactive sign-in (if you are signing in actual human users), or pursuing the Client Credentials Grant flow (if this is a daemon/unattended service).
You are not getting an ID Token because the flow you've invoked (the OAuth 2.0 Resource Owner Password Credentials Grant flow), does not define any concept of an ID Token, or an id_token response. In other words: you haven't asked for one.
If you add scope=openid to your Authorization Request, you'll be hinting to Azure AD that you're more or less interested in knowing stuff about the person who signed in, and you'll get an unsigned OpenID Connect ID Token in the response.
To https://jwt.io to verify the claims:
Ensure this is a token intended for you. (i.e. don't expect to be able to decode and verify a token that was not intended for you (where "you" are the resource in the Authorization Request, and the aud in the token claims).
Ensure you've selected the correct signing algorithm (RS256).
Ensure you're checking against the key with which the token was signed (use the kid header value from the JWT as a hint).
Ensure the certificate ends in -----END CERTIFICATE----- (I've found jwt.io doesn't care too much about how it starts.
Double-check your copy-pasting, it's easy to accidentally pick up extra characters.