How to check REST requests for JWT token issued by Keycloak in JAX-RS application - jax-rs

I'm aware that this question might not be explicitly about "programming" but I have some trouble
understanding the concept. Maybe you're able to help.
Basically, I'm deploying a WAR offering several REST endpoints based on JAX-RS to a WildFly 20 server.
Here is the baseline:
Endpoints must be secured.
A frontend client (based on React) shall be able to access this API.
Keycloak is being used to authenticate users of the React app.
This is what I have so far:
React client
The app integrates the keycloak-js adapter. Any login is routed through Keycloak which issues a JWT token which is used for all further requests by the app, i.e. they contain an Authorization: Bearer <token> header. Check.
JAX-RS application in WildFly
web.xml
<security-constraint>
<web-resource-collection>
<web-resource-name>api</web-resource-name>
<url-pattern>/users/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>*</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>KEYCLOAK</auth-method>
<realm-name>this is ignored currently</realm-name>
</login-config>
<security-role>
<role-name>*</role-name>
</security-role>
keycloak.json
{
"realm": "my-realm",
"auth-server-url": "${keycloak.url}/auth",
"ssl-required": "none",
"resource": "my-client",
"public-client": true,
"confidential-port": 0,
"principal-attribute" : "preferred_username",
"enable-cors": true
}
The (showcase) problem
The initial request to http://localhost:8090/api/users redirects the user the Keycloak login. After a successful login, the same URL is accessible without sending the authentication header. That's the point where I'm stuck. My understanding is that I need to tell my server application "Hey, check any incoming request to this URL if it contains the valid JWT token issued by Keycloak".
I assume that there are ways so that WildFly can handle exactly this. Or do I have to implement some JWT validation service my own?

If you don't want to gets redirected to login page at all and the /users path is not used to serve any web contents (e.g. jsp, html, etc.), then it's safe to tell the Keycloak that /users is only accessible via "Bearer" tokens. Then in cases that such a token doesn't exist in the request, it just returns proper HTTP status code and doesn't do anything. You can do this by setting bearer-only property to true in the keycloak.json file. So your file should look like this:
{
"realm": "my-realm",
"auth-server-url": "${keycloak.url}/auth",
"ssl-required": "none",
"resource": "my-client",
"public-client": true,
"confidential-port": 0,
"principal-attribute" : "preferred_username",
"enable-cors": true,
"bearer-only": true
}

Related

Keycloak: Authorization between services and the public frontend

I have an application which consists of a frontend and several backend services. The authentication is done via Keycloak.
The workflow looks like this:
The user logs into the frontend and gets a token from Keycloak. This token is sent to the backend with every request.
The following image explains the current architecture:
In Keycloak I have the following clients:
1. Frontend
Access Type: public
Client Protocol: openid-connect
2. Core Service
Access Type: bearer-only
Client Protocol: openid-connect
3. User Service
Access Type: bearer-only
Client Protocol: openid-connect
How can I validate calls between services now?
I would imagine something like a service account and these have the possibility to call each other independently from the bearer-token from the frontend. The problem is that both services can be called from the frontend as well as between each other.
Edit:
My API is written with NestJS.
The API of the user-service:
And this is how I call the user-service in my core-service:
and this is my keycloak configuration for the the user-service:
At the moment I don't add anything to the request and I don't have any extra configuration on the interface. So I added the #Resource('user-service')-Annotation to the Controller and the #Scope()-Annotation to the Endpoint.
After that I don't get an error immediately and the endpoint is called.I can log that the logic is executed. But as response I still get a 401 Unauthorized Error.
Do I need to specify a scope or what do I need to add in the #Resource-Annotation?
Edit 2:
I'll try to show you my current situation with many screenshots.
Initial situation
Here is your drawing again. For me, points 1-5 work and point 8 works even if I do not call another service.
My Configuration
That this works, I have the following configuration:
Just Frontend and Core Service
Frontend:
Core-Service:
For the core service (gutachten-backend), I do not need to make any further configurations for this. I also have 2 different roles and I can specify them within the API.
Using Postman I send a request to the API and get the token from http://KEYCLOAK-SERVER_URL/auth/realms/REALM_NAME/protocol/openid-connect/token.
These are my 2 testing methods. I call the first one and it works. The following is logged. Means the token is validated received and I get Access:
Calling the user service
Now I call the second method. This method calls the user-service.
This is my request in the core-service:
I do not add anything else to my request. Like a bearer token in the header.
The endpoint in the user service is just a test method which logs a message.
This is my configuration for the user service:
I have now tried something with resources, policies and permissions.
Resource
Policies
Role-Policy
Client-Policy:
Permission
And analogously the client permission
Questions and thoughts
All steps from the first drawing seem to work except 6 and 7
Do I need to add more information to my request from core service to user service?
How to deal with root url and resource urls?
In the code in the API, do I need to additionally configure the endpoints and specify the specific resources and policies? (NestJS offers the possibility to provide controllers with a #Resource('<name>') and endpoints with #Scopes([<list>]))
Additionally, through a tutorial on setting up keyacloak in NestJS, I turned on the following config:
This adds a global level resource guard, which is permissive.
Only controllers annotated with #Resource and
methods with #Scopes are handled by this guard.
Keycloak's Token Verification API can do it.
This is one of Architecture for Authorization of resource access permission.
Between Core Service and User Service, Core Service needs to verify the access-token to Keycloak.
It means this token can access the User service API Yes(Allow) or No(Deny)
This is API format
curl -X POST \
http://${host}:${port}/realms/${realm}/protocol/openid-connect/token \
-H "Authorization: Bearer ${access_token}" \
--data "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \
--data "audience={resource_server_client_id}" \
--data "permission=Resource A#Scope A" \
--data "permission=Resource B#Scope B"
Demo Keycloak Token URL: localhost:8180
Authorization Enabled Realm: test
Authorization Enabled Client: core-service
Client Resource: resource:user-service
User1 : can access it (ALLOW) password: 1234
User2 : can access it (ALLOW) password:1234
Steps
Get User Access Token(instead of login) ->
Preparations
ready to assign access-token(named user-token) variable in Postman
var jsonData = JSON.parse(responseBody);
postman.setEnvironmentVariable("user-token", jsonData.access_token);
Get Token URL from Keycloak UI, click the Endpoints
Get User1's access token
with Bearer Token option with {{user-token}} in Authorization Tab
Verify with user1 token from Core Service to Keycloak
return 200 OK from Keycloak (ALLOW) - it is Circle 4 and 5 in my Architecture.
So Core Service forward API call to User Service for accessing service
Note - needs to finish Keycloak Permission setting
Verify with user2 token from Core Service to Keycloak
return 200 OK from Keycloak (Allow) too.
So Core Service return an error to Front-end, like this user can't access a resource of User Service.
More detail information is in here
Keycloak Permission setting
Create Client
Create Client Resource
Add Client Role
Add Client Policy
Add Permission
All user mapping into Client role
This is Configuration in Keycloak
Create Client
Create Client Resource
Add Client Role
Add Client Policy - role based
Add Permission
All user mapping into Client role - any user if you want to add to access the resource.
For people who have the same problem in the future. The answer from #BenchVue helped a lot to understand the concept in general.
What was missing is that a token must also be added for each request between services. Namely the token of the client.
So before the request is sent, the following query takes place. This is the method to get the token for a client:
getAccessToken(): Observable<string> {
const header = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
};
return this.httpService.post(
'{{keycloakurl}}/auth/realms/{{realm}}/protocol/openid-connect/token',
`grant_type=client_credentials&client_id={{clientId}}&client_secret={{clientSecret}}`,
header).pipe(
map((response) => {
return response.data.access_token as string;
}
));
}

Custom domain for "cognito-idp.us-east-1.amazonaws.com"

I have a Cognito app client configured to use USER_PASSWORD_AUTH flow. By POSTing this request:
{
"AuthParameters": {
"USERNAME": "{{Username}}",
"PASSWORD": "{{Password}}"
},
"AuthFlow": "USER_PASSWORD_AUTH",
"ClientId": "{{AppClientId}}"
}
to "cognito-idp.us-east-1.amazonaws.com", I am able to successfully authenticate and retrieve JWTs.
I would like to CNAME the URL to be something like "auth.mydomain.com", but when I do that, I get a client certificate validation error. Is there anyway to associate a valid certificate so I can CNAME the URL successfully?
You can configure a custom domain within your Cognito user pool. That's what we had to do to make this work. Check out this Cognito documentation. It discuses using the hosted UI stuff, but it should also apply to your scenario where you provide the login UI.

facebook oauth error using keycloak Can't load URL: The domain of this URL isn't included in the app's domains

I am trying to implement social login using keycloak in a react-native application and upon following official keycloak documentation I have hit a dead end.
I have correctly configured (according to documentation) the correct redirect URI.
Details-
1.created a new facebook app. Now I have two different web resources.
a.) An instance running keycloak server on a docker setup.
b.) A dummy web app on a different domain that I am redirecting to using my react native code. I am using react-native-login package and the configuration which goes into their Login.start(config) method is attached below.
2.The website uri is the facebook app setting is same as the redirect uri that keycloak provides upon adding an identity provider.I have double checked my app id and app secret
and switched the app to production by giving it a privacy policy on the dummy express aplication I am trying to redirect to.
3.The keycloak server and the dummmy express app I have are on different domains (I don't see any problems with this but the tutorial I was following had the website and the keycloak server on the same domain with different sub domains could this be the problem).
The error
{
"error": {
"message": "Can't load URL: The domain of this URL isn't included in the app's domains. To be able to load this URL, add all domains and sub-domains of your app to the App Domains field in your app settings.",
"type": "OAuthException",
"code": 191,
"fbtrace_id": "Awr-3dCV3zr"
}
}
config object
const config = {
url: "https://authserver.ml/auth",
realm: "Demorealm",
client_id: "demoFrontEnd",
redirect_uri: "https://dummyapp.ml/privacypolicy",
appsite_uri: "https://dummyapp.ml",
kc_idp_hint: "facebook",
};
website URI in facebook = redirect uri by keycloak (according to keycloak documentation)
added both the domains in the domains section of facebook app settings under the basic tab.
In the Share Redirect Domain Allow List section in advanced settings in facebook app I have added both the URI's ie. the authentication broker url from keycloak and the uri I am trying to redirect to after a successful login but I get this error everytime
Another scenario I have noticed is when I try to give the react-native-login config object a redirection uri that is mentioned in keycloak I get the invalid parameter error from keycloak
NOte- the error is arising from facebook's graph api which means that the authentication request went past the keycloak server and to facebook (https://graph.facebook.com/oauth/authorize?scope=email&state=rest of the url)
Any suggestions at this point will be much appreciated.

How to implement OIDC using vuex-oidc in vue.js

We are trying to implement OIDC for user onboarding from our main platform to our secondary platform
While calling AutomaticSilentRenew function it leaves the following error:
Is there anything wrong here (PFA)
OIDC config Object:
VUE_APP_OIDC_CONFIG={"authority": "https://auth.mainPlatform.com", "clientId": "<client-id>", "redirectUri": "http://localhost:8080/oidc-callback", "popupRedirectUri": "http://localhost:8080/oidc-popup-callback", "responseType": "id_token token", "scope": "openid email", "automaticSilentRenew": true, "automaticSilentSignin": true, "silentRedirectUri": "http://localhost:8080/silent-renew-oidc.html"}
Error:
{context: "authenticateOidcSilent", error: "login_required"} App.vue?234e:38 I am listening to the oidc error event in vuex-oidc context: "authenticateOidcSilent" error: "login_required"
That's an absolutely standard part of the silent renewal flow and can happen for 2 reasons:
When the Authorization Server Session Cookie expires
If a browser drops the AS session cookie - eg Safari is likely to do this in 2020
The usual action when you get a login_required error code is to redirect the user to sign in again.
TROUBLESHOOTING
If this is happening on every token renewal request, I would debug via a tool such as Fiddler to see if the cookie is being sent. Your problem might be caused by recent browser restrictions on cross domain cookies.
For something to compare against, see my Silent Token Renewal blog post.

How do you refresh a JWT Token when the user authenticated with Cognito Hosted UI in AWS?

Details first:
Environment = Cognito Hosted UI
Situation = User signs in using it
Result = He's successfully authenticated and is redirected to whatever URL to which AWS adds the parameter "id_token=" with whatever value
Sample whatever value after decrypting that token with jwt.io =
{
"at_hash": "some_value_here",
"sub": "the_sub_id_in_cognito",
"aud": "the_client_id_of_the_cognito_app",
"email_verified": true,
"token_use": "id",
"auth_time": 1573661803,
"iss": "https://cognito-idp.us-east-1.amazonaws.com/AWSRegion_CognitoPoolName",
"name": "FirstName LastName",
"cognito:username": "the_username_in_cognito",
"exp": 1573665403,
"iat": 1573661803,
"email": "the_email_in_cognito"
}
My question = This token expires within one hour (you can't change this). And in order to keep the user authenticated for more than one hour, you'd have to submit a refresh token using the Cognito InitiateAuth API.
All fine and dandy, except I don't see any refresh token in that JSON :|
Where do I get that refresh token value ? Is it something that gets generated once (and once only) at initial user registration and I should store it in some database ?
Or is that token NOT generated by Cognito Hosted UI because it's not supported and I should stop using it altogether ?
PS: On a different note -> Is Cognito Hosted UI any good or is it bull-crap and I should stop looking into it altogether ?
Thanks and hope you can help me on this ...
Marius
Update.
I've changed my logic to continue to use Cognito Hosted UI and "generate" the HTML pages from Lambda.
I've used the logic from here -> https://github.com/aws/chalice/issues/717 to fetch the authentication code and store them as JWT tokens in cookies using Lambda.
I've also had to modify the App Client Settings in Cognito as following:
Allowed OAuth Flows = Just Authorization code grant
Basically the logic of using JWT tokens now is being generated "manually" at the Lambda layer.
Hope this helps someone out there...