I have a mobile app(react-native), a resource service(spring boot) and Keycloak Authenticatioin Service(Auth-Service).
Client makes authentication directly with Auth-Service and gets the access token.
When I do a request to the resource service, the resource service checks the access token by asking to the Auth-Service. But token obtained by the client app and iss field is http://10.0.2.2:8060/auth/realms/sau and my resource service at http://localhost:8110.
Keycloak says: error="invalid_token", error_description="Invalid token issuer. Expected 'http://localhost:8060/auth/realms/sau', but was 'http://10.0.2.2:8060/auth/realms/sau'"
My question is how can I make authentication in resource service behalf my client?
Mobile App:
export const prepareRequestBody = credentials => {
const params = new URLSearchParams();
params.append('username', credentials.username);
params.append('password', credentials.password);
params.append('client_id', "nasilim-mobile-app");
params.append('grant_type', "password");
return params;
};
export const login = credentials => {
const params = prepareRequestBody(credentials);
return axios.post(LOGIN, params);
};
Resource-Service:
application.yml
keycloak:
realm: sau
resource: photo-service
bearer-only: false
auth-server-url: http://localhost:8060/auth
credentials:
secret: 69a3e80c-8360-42df-bcec-b6575a6949dc
Note: I have checked this question and I have tried to set "X-Forwarded-For" : "http://localhost:8060/" but It didn't work Keycloak returns:
{
"error": "invalid_request",
"error_description": "HTTPS required"
}
Here is a Sample Access Token that obtained by mobile client.
The "iss" claim vary in function of the request. The variable KEYCLOAK_FRONTEND_URL can change this behavior. So try do as follow in your docker-compose file:
KEYCLOAK_FRONTEND_URL: http://10.0.2.2:8060/auth
You need to configure access from your Spring Boot app to the Auth server in an external fashion, not localhost:
keycloak:
realm: sau
resource: photo-service
bearer-only: false
auth-server-url: http://10.0.2.2:8060/auth
credentials:
secret: 69a3e80c-8360-42df-bcec-b6575a6949dc
This way the token issuers will match. This will probably require either to disable SSL requirement for external request in keycloak or to configure proper SSL communication. If this is meant for production, do the right way.
See also:
https://stackoverflow.com/a/42504805/1199132
What if you can not access the auth server using the external address? This will not work. Check https://issues.redhat.com/browse/KEYCLOAK-6984
One workaround is to set the reaml public key. But it's not recommended as the adapter will not check for new key if the key is rotated.
Use the proxy-url in the adapter configuration to provide an alternative URL.
See docs.
Spring application.yml:
keycloak:
authServerUrl: http://10.0.2.2:8060/auth
proxyUrl: http://localhost:8060/auth
...
Or keycloak.json:
{
"auth-server-url": "http://10.0.2.2:8060/auth",
"proxy-url": "http://localhost:8060/auth",
...
}
At keycloak go to your realm then to realm settings and place the url you want at frontend url.
Problem is because inside "container" there is different ip(host).
Keycloak has property hostname-url. You should inform keycloak about your frontend(hosts). You can do that during starting new instance of keycloak. Command to start your keycloak is start-dev --hostname-url=url-to-your-frontend
you can provide the hostname of frontend, so the command will be look like this:
start-dev --hostname-url=http://10.0.2.2:8060
This solution is working for Keycloak version 19 and up
Related
My application running on Kubernetes (AKS) has a working standard oAuth2 authentication flow, which I added using oAuth2-proxy and Keycloak. The password Credentials grant type / standard flow via the Browser is working fine. After the redirect to the KC login page and manual login, the oAuth2-proxy lets the user pass and and application page (echo server) is shown.
Now I am trying to use Grant type client credentials, e.g from Postman or Curl. I have enabled 'Service Accounts Enabled'. I can retrieve the access_token / bearer token without issues and am including it in the header "Authorization'. I can see that the token is valid and other contents also looks correct, but the request does not pass. The oauth2-proxy redirects the request to the login page.
oAuth2-proxy parameters:
- --provider=keycloak-oidc
- --client-id=nginx
- --client-secret=topsecret
- --redirect-url=https://my-redirect-url
- --oidc-issuer-url=https://myurl
- --silence-ping-logging
- --auth-logging=true
- --session-store-type=redis
- --set-xauthrequest=true
- --set-authorization-header=true
- --pass-authorization-header=true
- --pass-access-token=true
- --insecure-oidc-allow-unverified-email
- --show-debug-on-error
- --errors-to-info-log
- --cookie-secret=gf...
- --cookie-httponly=false
- --force-json-errors
I am not sure if need to include this script in the Ingress or not:
# nginx.ingress.kubernetes.io/configuration-snippet: |
# auth_request_set $name_upstream_1 $upstream_cookie__oauth2_proxy_1;
# access_by_lua_block {
# if ngx.var.name_upstream_1 ~= "" then
# ngx.header["Set-Cookie"] = "_oauth2_proxy_1=" .. ngx.var.name_upstream_1 .. ngx.var.auth_cookie:match("(; .*)")
# end
# }
Candidate root causes:
the token does not contain what is checked (KC config...)
the token is not included in the request in the right way
the oAuth2-proxy config is missing something to get the token correctly from the request
oAuth2-proxy cannot validate the token against Keycloak
I can access the Keycloak, Nginx and oauth2-proxy logs. So far the oauth2-proxy logs helped to solve earlier issues, but the whole time I am missing a better what to analyze what is going on. I wish there was a trace log level on oAuth2-proxy which I can enable via an env var using my Helm values file, but the available options in the doc did not seem suitable.
What am I missing? How can I analyze this issue better? Or already any suggestions about the root cause / fix?
You need to enable resource server mode (--skip-jwt-bearer-tokens = true flag).
Im using this gem to add Omniauth OpenID with a provider.
I configured the gem in the Devise Initializer, everything seems to be correct:
config.omniauth :openid_connect,
{
name: :openid_connect,
scope: %i[openid profile groups_rewardops scope_rewardops],
issuer: ConfigSettings.desjardins.issuer_url,
response_type: :code,
uid_field: 'sub',
response_mode: :query,
discovery: true,
send_scope_to_token_endpoint: false,
client_options:
{
port: 443,
scheme: "https",
host: ConfigSettings.desjardins.host,
authorization_endpoint: "/affwebservices/CASSO/oidc/rewardops/authorize",
token_endpoint: "/affwebservices/CASSO/oidc/rewardops/token",
userinfo_endpoint: "/affwebservices/CASSO/oidc/rewardops/userinfo",
identifier: ConfigSettings.desjardins.client_id,
secret: ConfigSettings.desjardins.client_secret,
redirect_uri: "#{ConfigSettings.api.base_url}front_end/users/auth/openid_connect/callback",
},
}
The flow I have atm is that the user can log in and grant access from the provider, then the provider sends a request to my devise callback url with the nonce, code and state. At this point everything seems to be correct but that request ends in failure when trying to generate the access_token with the following error:
ERROR -- omniauth: (openid_connect) Authentication failure! invalid_request: Rack::OAuth2::Client::Error, invalid_request :: Client credentials are invalid.
Im sure the identifier and the secret are correct, don't understand what's going on.
Since Im using discovery mode all the configs of the provider are in the .well-known you can check it here
Im blocked without ideas about how to debug the error. Checking at Rack::OAuth2 to see where the error is comming from I found this that says:
invalid_request: "The request is missing a required parameter, includes an unsupported parameter or parameter value, repeats the same parameter, uses more than one method for including an access token, or is otherwise malformed.",
It seems for some reason the access token request is malformed, but not sure what else apart of identifier and secret should I have in mind? I have seen many other examples of configuration and mine seems to be correct.
Since you are sure your credentials are correct, I suspect there is mismatch between the authentication method being used and the methods supported by the provider. Checking the .well-known config, I see this provider only supports client_secret_post. In your omniauth config, I see no options being passed to specify the authentication method. When I dive down into the code, I see that the underlying oauth2 gem defaults to using basic auth, which uses the indentifier and secret to construct an Authentication header. See: source code here
client_auth_method = args.first || options.delete(:client_auth_method).try(:to_sym) || :basic
case client_auth_method
when :basic
cred = Base64.strict_encode64 [
Util.www_form_url_encode(identifier),
Util.www_form_url_encode(secret)
].join(':')
headers.merge!(
'Authorization' => "Basic #{cred}"
)
In the client_secret_post authentication method, instead of providing client secret in the header, the client authorizes itself providing the secret in the HTTP request body as a form parameter. So this provider is not seeing your credentials. You could verify this by looking at the logs of the token endpoint request, which won't be visible in the browser, but rather from your rails BE to the the provider's server.
Try passing a client_auth_method in the client_options hash in your omniauth config. If you look at the case statement in the code I linked to above, there doesn't seem to be a named option for client_secret_post, but it is the default case. Any value for client_auth_method looks like it would work, but I would still use client_secret_post.
I have a spring backend which i'm accessing my Elastic search cluster through by a proxylike endpoint. The request has to be authorized with a cookie.
I'm currently using searchkit with supports authenticating requests through the withCredentials flag. Is there a similar option for reactivesearch or is there any other solution for authorizing the request with a cookie?
I could add: the backend exposes a swagger client which runs on a different domain than my frontend client. This client "owns" the cookie and thus i cannot read the cookie from my frontend client
You can use the headers prop in ReactiveBase to pass custom headers along with the requests. Link to docs. Since there is no withCredentials you could read the cookies and set in custom headers to verify the requests at the proxy middleware.
<ReactiveBase
...
headers={{
customheader: 'abcxyz'
}}
>
<Component1 .. />
<Component2 .. />
</ReactiveBase>
Here is a sample proxy server but its in NodeJS
Okey so it turns out, Reactivesearch uses fetch and fetch wants credentials: 'include' for cookie authentication. This may not be placed in the headers that Reactivesearch supplies and must be placed on the root object for the request.
It's possible to do this by implementing beforeSend on ReactiveBase.
const Base = ({ children }) => {
const onBeforeSend = props => {
return {
...props,
credentials: 'include',
}
}
return (
<ReactiveBase
app="app-name"
url="url"
beforeSend={onBeforeSend}
>
{children}
</ReactiveBase>
)
}
Postman has Authentication helpers to help with authenticated calls and I'm trying to use the OAuth 2.0 helper to call a REST server created by JHipster using Spring (Security, Social, etc).
I've tried a lot of configurations, this is the screen (client ID and Secret were masked):
For the Authorization URL I've tried:
http://127.0.0.1:8080/oauth/authorize
http://127.0.0.1:8080/#/login (the app's login route)
The closer I get from receiving a token back to Postman is:
I don't know why it's erring like this. Maybe I'm setting the Callback URL incorrectly? Do I need to do this in the server or in the client (AngularJS)?
Does anyone have any idea of what's wrong? I appreciate your help.
JHipster is currently setup to use the "password" oauth2 grant type. The helper oauth2 helper only seems to work with "authorization code" and "client credentials" grant types.
What you'll want to do is first call your app's token endpoint directly as the angular app does in
src/main/webapp/scripts/components/auth/provider/auth.oauth2.service.js
POST http://localhost:8080/oauth/token?username=MY_USERNAME&password=MY_PASSWORD&grant_type=password&scope=read%20write
where your username and password can be "user" and "user" respectively, for example and with one header set:
Authorization: Basic AAAAAA
where AAAAAA is your (clientId + ":" + clientSecret)--all base64-encoded. You can use https://www.base64encode.org/. For example if your clientId is "jhipsterapp" and your clientSecret is "mySecretOAuthSecret", replace AAAAAA with "amhpcHN0ZXJhcHA6bXlTZWNyZXRPQXV0aFNlY3JldA==" since that is "jhipsterapp:mySecretOAuthSecret" base64-encoded.
That should return you an access_token. Now hit your API endpoints by calling them with the access_token from your password request in your header like this.
Authorization: Bearer access_token_from_earlier_token_request
Update: if you're using microservices and UAA, then see Niel's answer https://stackoverflow.com/a/45549789/1098564
To build on #sdoxsee's answer:
Currently (August 2017) JHipster generates a class called UaaConfiguration with the configure(ClientDetailsServiceConfigurer) method setting up the client ID, client secret, scope and grant type. Refer to these settings (including the referenced JHipster properties in the application*.yml) to populate the Postman authentication helper, using /oauth/token as both Auth URL and Access Token URL.
Example:
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
/*
For a better client design, this should be done by a ClientDetailsService (similar to UserDetailsService).
*/
clients.inMemory()
.withClient("web_app")
.scopes("openid")
.autoApprove(true)
.authorizedGrantTypes("implicit", "refresh_token", "password", "authorization_code")
.and()
.withClient(jHipsterProperties.getSecurity().getClientAuthorization().getClientId())
.secret(jHipsterProperties.getSecurity().getClientAuthorization().getClientSecret())
.scopes("web-app")
.autoApprove(true)
.authorizedGrantTypes("client_credentials");
}
And,
jhipster:
security:
client-authorization:
client-id: internal
client-secret: internal
Means your authentication helper should be populated as follows:
I'm trying to implement a OAuth2 Authorization Server using DotNetOpenAuth. The client is JavaScript based thus incapable of holding any secrets. This is exactly the same problem like this question but with another framework.
The client requests (against the token endpoint) access_token and refresh_token with following parameters:
grant_type: password
username: foo
password: bar
This does work. Now I want use the refresh_token and make a request against the token endpoint with the following parameters:
grant_type: refresh_token
refresh_token: ABCDEF
This gives me the following response:
{"error":"invalid_client","error_description":"The client secret was incorrect."}
Which does make (at least some) sense because RFC6749 states that:
Because refresh tokens are typically long-lasting credentials used to
request additional access tokens, the refresh token is bound to the
client to which it was issued. If the client type is confidential or
the client was issued client credentials (or assigned other
authentication requirements), the client MUST authenticate with the
authorization server as described in Section 3.2.1.
If I change my request like so:
grant_type: refresh_token
refresh_token: ABCDEF
client_id: MYCLIENT
client_secret: CLIENT_SECRET
The problem is my client is not supposed to be confidential (because it is client side JavaScript after all).
This is how the client is defined:
New ClientDescription(ApiKey, New Uri(allowedCallback), ClientType.Public)
I searched through the DotNetOpenAuth source code and found no use of the ClientType. To me it looks like it is not used at all.
It is also not possible to the set an empty client secret, because the DotNetOpenAuth source code actively checkes against this (ClientAuthenticationModules.cs):
if (!string.IsNullOrEmpty(clientSecret)) {
if (client.IsValidClientSecret(clientSecret)) {
return ClientAuthenticationResult.ClientAuthenticated;
} else { // invalid client secret
return ClientAuthenticationResult.ClientAuthenticationRejected;
}
} else { // no client secret provided
return ClientAuthenticationResult.ClientIdNotAuthenticated;
}
If I take a look at MessageValidationBindingElement.cs:
if (authenticatedClientRequest != null) {
string clientIdentifier;
var result = this.clientAuthenticationModule.TryAuthenticateClient(this.AuthServerChannel.AuthorizationServer, authenticatedClientRequest, out clientIdentifier);
switch (result) {
case ClientAuthenticationResult.ClientAuthenticated:
break;
case ClientAuthenticationResult.NoAuthenticationRecognized:
case ClientAuthenticationResult.ClientIdNotAuthenticated:
// The only grant type that allows no client credentials is the resource owner credentials grant.
AuthServerUtilities.TokenEndpointVerify(resourceOwnerPasswordCarrier != null, accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.InvalidClient, this.clientAuthenticationModule, AuthServerStrings.ClientSecretMismatch);
break;
default:
AuthServerUtilities.TokenEndpointVerify(false, accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.InvalidClient, this.clientAuthenticationModule, AuthServerStrings.ClientSecretMismatch);
break;
}
Espacially the comment The only grant type that allows no client credentials is the resource owner credentials grant. makes me wonder. Does that mean that in my scenario the JS client should send username/password along? Nope this will raise the following exception:
AccessTokenResourceOwnerPasswordCredentialsRequest parameter 'grant_type' to have value 'password' but had 'refresh_token' instead.
Which is okay to me, because I don't want the client to keep the password.
So here my questions:
Did I unterstand something fundamentally wrong about the password-grant, refresh_token scheme?
As I see it in a JS client the client_id is public knowledge, so it does not serve any security purpose. Am I correct?
Does it makes sense to change DotNetOpenAuth to make use of the ClientType.Public?
Would it make any difference if I just use client_id and client_secret as not secret? E.g. just supply dummy values? What are the security implications?