I am trying to do some testing using keycloak and go in a local docker setup. Currently, I have a keycloak container (with a postgres backend) running on 8080. I set this up with a client that has service account access, and have my ClientID, client secret, realm etc all in a config file.
I then have a go API running in docker as well. In this go API, at startup I create a new gocloak client and hold reference to it:
client := gocloak.NewClient(host)
log.Infof("clientid: %s pw: %s // realm: %s", clientId, clientSecret, realm)
ctx := context.Background()
tkn, err := client.LoginClient(ctx, clientId, clientSecret, realm)
if err != nil {
log.Errorf("error occurred connecting to keycloack: %s", err)
}
The value of host above is:
http://keycloak:8080
as the docker container running keycloak is just called keycloak. Obviosuly if i Try and use localhost:8080 here, it fails because we are inside a container.
And then as a middleware on any calls coming in to this service's routes, I use that client and check:
authHeader := c.GetHeader("Authorization")
parts := strings.Split(authHeader, " ")
if len(parts) != 2 {
c.AbortWithStatus(401)
return
}
ctx := context.Background()
kc.Logger.Infof("Incoming auth header: %s", parts[1])
token, err := kc.Auth.RetrospectToken(ctx, parts[1], kc.ClientId, kc.ClientSecret, kc.Realm)
if err != nil {
kc.Logger.Errorf("error occurred parsing token: %s", err)
}
kc.Logger.Infof("token: %v", token)
In order to test this, what I have been doing is starting up all the services, and then navigating to:
http://localhost:8080/auth/realms/dev/account/#/
and then I sign in with google (an account I have already set up on keycloak earlier).
Then, using the developer tools in the browser, im finding a network call that contains
KEYCLOAK_IDENTITY=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiaSldUIiwia2lkIiA6ICIwMTg3MzU0MS03YmE3LTQ0MmQtYjMxOC0zMjBhYmIzYjNmMmIi<AND_SO_ON>; Version=1; Path=/auth/realms/dev/; SameSite=None; Secure; HttpOnly
From this i extract just the token, and then use that as a Bearer token in a postman call to my go back end.
The problem I am having is that, in step 1 when I log in my service account, if i retrospect the token returned there, I see the struct I'd expect with the oauth2 token values.
This middleware, when it retrospects the incoming tokens I give it, i just get
{
active: false
}
every time.
I am not entirely sure where I have gone wrong here. I have read a number of posts that say the host has to be the EXACT SAME between the acquire token, and the endpoint used to inspect it, which leads me to beleive that since I get a token from:
http://localhost:8080/auth/realms/dev/account/#/
but am decoding it on a host inside the container thats called:
http://keycloack:8080
That this may be my problem? Is this accurate? or have I gone wrong somewhere else?
If this IS in fact the problem, how can I go about solving this? I can't get a token from the internal docker name from the outside in order to test calls going in to my API, and I cannot use "localhost" inside the go container to match where i get it from as this wont resolve.
How can I either get a hostname i can access from both inside and outside the container?
Or, as mentioned, have I gone wrong somewhere else entirely?
Any help will be appreciated!
Related
This is an issue I've been trying to figure out for a few days now.
Service-to-service communication always results in 401 for my Cloud Run applications.
For token retrieval in service I've been using this code snippet:
tokenURL := fmt.Sprintf("/instance/service-accounts/default/identity?audience=%s", c.serviceURL)
var err error
token, err = metadata.Get(tokenURL)
if err != nil {
return nil, fmt.Errorf("metadata.Get: failed to query id_token: %+v", err)
}
I have also tried to supply a Service account key to my Cloud Run service and using the token that is returned from google.JWTAccessTokenSourceFromJSON to no avail.
The problem is that the JWT that is returned does work when used from cURL, but does not work when used in a service that runs on Cloud Run.
Both the Cloud Run service account and the external service account (for the key) has the roles/run.invoker IAM binding. And the audience is the Cloud Run issued service URL.
I have tried the following:
Using the metadata service for token retrieval (401 inside Cloud Run, but works when the token is used in cURL and Postman)
Using the JWTAccessTokenSourceFromJSON for token retrieval (401 inside Cloud Run, but works when the token is used in cURL and Postman)
Tried adding a delay before using issued token (in case the token is not propagated)
Tried manually pinging the Cloud Run service a few times with given token after the delay (using http.Client)
Nothing seems to work. Every request when run inside Cloud Run is returning 401 and the logs show The request was not authorized to invoke this service. Read more at https://cloud.google.com/run/docs/securing/authenticating. While using cURL or Postman I am able to access the service.
I've tried these methods to invoke the service:
tokenURL := fmt.Sprintf("/instance/service-accounts/default/identity?audience=%s", c.serviceURL)
var err error
token, err := metadata.Get(tokenURL)
if err != nil {
return nil, fmt.Errorf("metadata.Get: failed to query id_token: %+v", err)
}
// First one (ping)
req, _ := http.NewRequest("GET", "{{serviceURL}}", nil)
req.Header.Set("Authorization", "Bearer: "+token)
l, _ := cr.Do(req)
m, _ := ioutil.ReadAll(l.Body)
logrus.Println(l.Header)
logrus.Println(string(m))
// RoundTripper
r.Header.Set("Authorization", "Bearer: "+token)
return c.r.RoundTrip(r)
I appreciate any answer.
Thanks!
While posting code examples I just realized I added an extra colon(:) after Bearer which makes this request fail. Can't believe I spent days to solve this!
I'm using Jhipster 4.13.3 with the Oauth2/OIDC option to generate a gateway connected keycloak.
When a service send a 401 status, redirection to /login doesn't executed.
An interceptor (auth-expired.interceptor.ts) is defined to handle the error, but it doesn't work correctly.
First question
In the if condition, error.json() doesn't contain path, so the condition is false and the redirection is never executed.
if (error.status === 401 && error.text() !== ''
&& error.json().path && !error.json().path.includes('/api/account')) {
const destination = this.stateStorageService.getDestinationState();
if (destination !== null) {
const to = destination.destination;
const toParams = destination.params;
if (to.name === 'accessdenied') {
this.stateStorageService.storePreviousState(to.name, toParams);
}
} else {
this.stateStorageService.storeUrl('/');
}
const loginService: LoginService = this.injector.get(LoginService);
loginService.login();
}
Somebody knows the correct condition ?
Second question
I disabled the check on "error.json().path...", the redirection is called but fails "often", it works sometimes, I didn't found a explanation. Even if I kill all sessions in the keycloak admin console, the browsers redirects to the home page, not the keycloak login form.
Does somebody have an explanation about this ?
Thanks,
Philippe
We kept getting HTTP 401 status code responses when the hostname stamped on the iss field of the bearer's/user's access token had a different case i.e. lowercase vs. uppercase than the hostname in the URL used to post an HTTP request to keycloak's token endpoint.
Any kind of hostname mismatch can cause 401 errors:
Short hostname alias vs. fully qualified hostname e.g. https://myhost:8080 vs. https://myhost.domain.com:8080
https://MYHOST:8080 vs. https://myhost:8080
https://Myhost:8080 vs. https://myhost:8080
etc.
This happened in a Microsoft Windows 10 version 1809 environment.
I made a new topic about my issue.
KIM told\
Anonymous requests typically means that it does not find a
username/password not a token in the clients request. Remember the
Token you get on your first request should be reused for all
subsequent requests by all client code (all kbmMWSimpleClient,
kbmMWClientQuery, kbmMWClientResolver etc). On way to centralize that
is to put a TkbmMWSimpleClient on the datamodule and specify all the
client query components to use this simple client instance as a
template. Then as the first thing before anything else in your client
application, make an initial "login" request call via the
simpleclient.
I changed ServerSideQueryAllClick on the client app. I copied Token from server side to client Edit1.text.
procedure TForm1.btnNamedServerSideQueryAllClick(Sender: TObject);
begin
// Gets all records from server event table.
If Length(Trim(Edit1.Text)) > 0 then
Begin
kbmMWSimpleClient1.Disconnect;
kbmMWSimpleClient1.Username:= CB1.Text; // Login -> Franz
kbmMWSimpleClient1.Password:= CB2.Text; // Password -> FranzPassword
kbmMWSimpleClient1.Token := Edit1.Text; // Token from server
kbmMWSimpleClient1.Connect;
End;
if qServerSide.Active then qServerSide.Close;
// Use named query.
qServerSide.Query.Text:='#ALL_EVENTS';
qServerSide.Open;
end;
It dowsn't work.
How to make relogin?
The Authorization demo shows how the client has a simpleclient that is used as template for all the client query components (by setting their Client property to point at the simpleclient instance).
When setting the token, you specifically do not want to set the username or password, and similarly if you are setting username and password, do not set the token.
Also make sure that qServerSide.Client points on your simpleclient.
Doing that you generally only need to setup username/password once on the simpleclient before anything is opened, then for example open the query component, after which the simpleclient.token value will automatically have been updated from the server with the assigned login token.
I'm trying to use Google OAuth with Sign in & Sign Up for my Web Server Application.
This is the page : https://developers.google.com/identity/sign-in/web/backend-auth that I have referenced, but I am stuck in using the Google Client API, the TokenVerifier that is mentioned below in the document. I tried to find some examples, but I couldn't find one, as I am not sure how to handle the parameters in the methods that the sample shows.
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
...
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
.setAudience(Arrays.asList(CLIENT_ID))
.build();
// (Receive idTokenString by HTTPS POST)
GoogleIdToken idToken = verifier.verify(idTokenString);
if (idToken != null) {
Payload payload = idToken.getPayload();
if (payload.getHostedDomain().equals(APPS_DOMAIN_NAME)
// If multiple clients access the backend server:
&& Arrays.asList(ANDROID_CLIENT_ID, IOS_CLIENT_ID).contains(payload.getAuthorizedParty())) {
System.out.println("User ID: " + payload.getSubject());
} else {
System.out.println("Invalid ID token.");
}
} else {
System.out.println("Invalid ID token.");
}
For example, I know what these CLIENT_ID, ANDROID_CLIENT_ID, IOS_CLIENT_ID parameters mean in the sample code(in the reference page), but the server only receives id_token, which is basically a String Text. (That was made by the google api in the client-side, the javascript)
So, I do not have these parameter values passed to the server from the client. I know that google shows another way: the tokeninfo endpoint, but they mentioned that it is for only 100user/month only. (If I translated it correctly) However, for the tokeninfo endpoint way, they return the JSON file of containing client ids, which I think that would be the values for the parameters that I mentioned before, but I do not want to use the token info endpoint method.
So, my question is, how do I get the right parameter values for the sample code that is showed in the google document page? I only receive id_token value from the client.
ANDROID_CLIENT_ID or IOS_CLIENT_ID should be hard coded (in a config file) in your server's code.
Essentially your server is getting an id_token in a request and you need to make sure if it is meant for your app or server by checking the audience in there and making sure it matches one of the values you are expecting.
I have implemented an authorization server based on the sample and am receiving an access token in response to client credentials request. From my understanding this access token has a null username because it is not tied to a user.
I have implemented a resource server also based on the sample. When I try to validate the access token in my wcf server (resource server) in OAuthAuthorizationManager.VerifyOAuth2 I get an ArgumentNullException for username from
var error = resourceServer.VerifyAccess(httpRequestInfo, out result);
How can I modify OAuthAuthorizationManager to allow a null username?
Do I create a generic principal on the fly and assign it to the scope in the token.
i.e. should I use
var error = resourceServer.VerifyAccess(httpRequestInfo, out userName, out scope);
instead?
This is an issue with DotNetOpenAuth v4.0. v4.1 has this issue fixed. It's not released yet, but you can snag a copy from NuGet if you point it at this channel:
http://teamcity.dotnetopenauth.net:82/guestAuth/app/nuget/v1/FeedService.svc