Hello! I'm new to the GoodData community slack, so...
# gooddata-cloud
d
Hello! I'm new to the GoodData community slack, sorry if I skipped some protocol before writing on this channel. I write in behalf of Indigo Tech Group to kindly ask for help with the integration with GoodData. We're currently trying to use the Cognito user pool as an identity provider for GoodData by using user pool client that we use for login to authenticate with GoodData via
TigerJwtAuthProvider
. We've followed all the steps laid out in the documentation and the API is still giving us a 401 when passed a JWT in the Authorization Bearer header. These are the steps we've followed so far: - Added the user client ID and secret to GoodData - Stored the user pool JWKs in the GoodData JWK store - Provisioned a user in both the Cognito user pool and in GoodData - Authenticated the user using the same client ID and secret - Used the generated JWT in the GoodData API (
GET api/v1/layout/organization
for testing purposes) The result is still the same: 401 Unauthorized. I tested through the
TigerJwtAuthProvider
implementation and it didn’t work either. Is there a step (or steps) we're missing? Should this approach work? Thanks in advance.
j
Hi Daniel, Can you please confirm if you are just using the
name
user attribute, as it is only required? Check to make sure you have added the name attribute to a Cognito record. If you are still facing issues, could you please provide a related traceID from the console?
d
Here's an example of how we're setting the user, the id property contains the Cognito user name, is this field what you're referring to?
j
d
Oh I see, hmm I'll let you know if I can configure that part on the user pool, thanks
Hello, quick follow up on this. Which token should be provided for the
TigerJwtAuthProvider
? is it the accessToken or the IdToken? I've tested with both and the response is 401 and is telling that there are invalid claims. In the case of the accessToken it makes sense because it does not include the
name
in the claims but the idToken does and it returns the same error (the image is the response headers when using the idToken, for the access token it was the same but it specified the
name
as the invalid claim)
This is the payload for the idToken (I redacted some parts):
Copy code
{
  "sub": "xxxx",
  "email_verified": true,
  "iss": "<https://cognito-idp.us-east-1.amazonaws.com/xxxx>",
  "cognito:username": "xxxx",
  "origin_jti": "444261ff-552d-41c0-8229-a70375f68242",
  "aud": "xxxx",
  "event_id": "50abaede-560b-4c6b-9f30-c226d5db4e89",
  "token_use": "id",
  "auth_time": 1722914505,
  "name": "Loc Admin",
  "exp": 1722915405,
  "iat": 1722914505,
  "jti": "6cd78d1c-a0b9-49f4-9357-82f48f87acb4",
  "email": "<mailto:example@email.com|example@email.com>"
}
Hello @Joseph Heun, so sorry to bother you, did you have a chance to check this? Thanks in advance
j
Hey Daniel, I’ve actually escalated this to our L2 technical support team and they are currently looking into it for you. They will follow up as soon as possible
d
Oh great, thank you 🙏
m
Watching this too (I work at same org)
@Joseph Heun is there any update on this? Sorry to bother, the issue is somewhat urgent for us
r
Heya Michael, Daniel, sorry for the delay! Radek from L2 here, I've been checking into this and should have more later today 🙂
🙌 1
m
Hey @Radek Novacek any update on this? Thanks!
Bump
r
Hey Michael, this is really looking a lot like an issue with the JWK - could you double check that it looks okay? The error comes up as
The JWT contains invalid claims: [name].
, but under the hood, this is only following
Signed JWT rejected: Another algorithm expected, or no matching key(s) found
.
d
Hello Radek, just to make sure before I retest, I should use the access token and no the id token, right? Also, the JWKs I stored were the ones from the user pool that's being used, is that a correct approach? or should we create new ones?
m
We launch our product in 9 days and this impacts a feature we’re launching with
r
Heya Michael, Daniel, the access token is the correct one to use, yes 👍 I think same goes for the JWKs but I'm trying to double check so that I can confirm 100%!
m
Thanks for your continued help Radek!
r
Hi again, one thing that could be helpful here - could you share with me over DM both a JWT (just in case, both access and ID to be extra sure), and the JWKs from the
/api/v1/entities/jwks
endpoint so that we can go over these and verify everything is as it should be? Many thanks in advance! 🙂
Just confirming I've received all the requested info, thank you! At first glance, the access tokens and the JWKs look good, though I'll be checking the contents with a senior coworker as well - I'll keep you updated 🙂
m
Thanks Radek!!
r
Hi all, our engineers have gone over the examples and the logs - a correction for my part, the correct token to use is indeed the ID token. Checking into the code that actually handles the error, unfortunately the reported phrase says almost exactly the opposite of what it detects as well -
The JWT contains invalid claims: [name].
is trying to report the "name" claim is missing entirely (which makes it invalid).. I know you mentioned you tried the ID tokens before, however from the logs, I can only find
Token is not a valid Jwt
errors. Do you think you could try to test the setup with ID tokens? Worst case, we get another error to follow, and if I get the token used alongside it, we should know exactly where to go next!
d
Hello Radek, I'll be testing with the idToken today, I'll send you the results
🤞 1
r
Hi David, how's the testing looking? 🙂
Also, additionally, may I ask why you went for a JWT-based approach to user login? There are use-cases to this for sure, but if you are able to get the ID token and match it to a user on GoodData side, you are most of the way there to a standard OIDC flow using Cognito. This is mostly for my own understanding though! 🙂
m
Hey @Radek Novacek, we are just trying to get silent login set up in our app. When a user navigates to a page in our app that contains a GoodData dashboard, we don’t want them to enter credentials. Do you have docs for using the ID token? We were guided to use a JWT-based approach by GoodData professional services. But if there is an easier, equally secure route, that would be great.
r
Heya @Michael Gray, I'm assuming your overall setup is something like this: • you have a user pool that is the same in Cognito and GoodData • your parent app uses OIDC login via Cognito to authenticate users • since you already have the auth session context, including the ID/access tokens for each user, you're trying to use these in a JWT flow for GoodData auth If the above is correct, then it should also be possible to use the same auth context with the GD.UI SDK. The idea is that you'd set up a classic OIDC flow to GoodData and verify that you can log in to the non-embedded environment first; afterwards, on the app side, you would wrap your app in a React context provider with the auth info, a little like this:
Copy code
ReactDOM.render(
  <React.StrictMode>
    <AuthProvider>
      <App />
    </AuthProvider>
  </React.StrictMode>,
  document.getElementById('root')
);
(the implementation can differ based on your app structure, but this would provide the auth context to the whole app - I found a 3rd party article that describes a seemingly good way to do this for Cognito here) That way, when you embed the actual GD.UI Dashboard, you can use the GD-provided authentication provider from the documentation. On loading the component, the login flow will recognize that there is no session, attempt to authenticate with Cognito, identify the current session from the parent app, and then load the Dashboard. It's worth keeping in mind that this approach can result in a brief flash of a "no session" error from GoodData, we are currently investigating the login flow implementation and improvements are likely going to arrive soon, but for the time being, it's possible to work around this error by checking for the state of the backend - a simplified example is here:
Copy code
const [isLoggedIn, setIsLoggedIn] = useState(false);

  useEffect(() => {
    backend.currentUser().getUser().then(() => setIsLoggedIn(true));
  }, []);

  return (
    <BackendProvider backend={backend}>
      <WorkspaceProvider workspace={workspaceId}>
        {isLoggedIn ? <Dashboard dashboard={dashboardId} /> : <div>{ a loading screen would go here }</div>}
      </WorkspaceProvider>
    </BackendProvider>
  );
The error mentioned above only shows up for the first load of the Dashboard in the session though, any subsequent switches to the tab should be seamless. Overall, since this relies on OIDC, it's as secure as any other OIDC flows are, and unless any unexpected issues arise, it's relatively straightforward as well! Please let me know your thoughts, I'd be happy to elaborate 🙂
m
Hey Radek thanks for this detailed info! I will make sure Daniel sees this and we’ll see what he decides to do 👍 Thanks for sticking with us on this issue for so long!
d
Hello Radek, sorry for the wait, our testing environment wasn't available, going back to what you asked about testing with the idToken generated at login, this is the result, the same as the last time I tested with the idToken
In dm I sent the value of the idToken used
m
Hey @Radek Novacek We’ve also followed your suggestion above to use auth context with the GD.UI SDK, and the GD-provided authentication provider from the documentation. We’ve wrapped our app in an AuthProvider where we store our CognitoUserSession as the context provider value to be consumed by the whole app. Here is our setup:
Copy code
export const ReportDashboard = () => {
  const authContextValues = useContext(AuthContext)
  console.log(authContextValues)

  const backend = tigerFactory()
    .onHostname(indigoGoodDataDomain)
    .withAuthentication(new ContextDeferredAuthProvider(redirectToTigerAuthentication))

  return (
    <BackendProvider backend={backend}>
      <WorkspaceProvider workspace={workspaceId}>
        <Dashboard dashboard={idRef(reportId)} />
      </WorkspaceProvider>
    </BackendProvider>
  )
}
The
authContextValues
is our stored CognitoUserSession and logs in this component as:
Copy code
const authContextValues = {
  idToken: {
    jwtToken: '*****',
    payload: {
      sub: '*****',
      iss: '*****',
      'cognito:username': '*****',
      origin_jti: '*****',
      aud: '*****',
      event_id: '*****',
      token_use: 'id',
      auth_time: 1724806746,
      name: '*****',
      exp: 1724807646,
      iat: 1724806746,
      jti: '*****',
    },
  },
  refreshToken: {
    token: '*****',
  },
  accessToken: {
    jwtToken: '*****',
    payload: {
      sub: '*****',
      iss: '*****',
      client_id: '*****',
      origin_jti: '*****',
      event_id: '*****',
      token_use: 'access',
      scope: '*****',
      auth_time: 1724806746,
      exp: 1724807646,
      iat: 1724806746,
      jti: '*****',
      username: '*****',
    },
  },
  clockDrift: 0,
}
With this set up, the
ContextDeferredAuthProvider
still isn’t able to authenticate to Cognito and we are redirected to tiger authentication url every time. After we complete that login flow, we are redirected back to our app and are able to see the dashboard correctly. Are there any other steps we are missing? Or what is the ContextDeferredAuthProvider expecting to consume from our AuthProvider that we are missing?
r
Hi Michael, I will send David a part of a sample implementation for this - using Auth0 as an example, but should be helpful to setting this up. There are some important considerations to this, but I think it's best communicated via code 🙂 For the test done with the idToken, we are getting
InvalidBearerTokenException: Token is not a valid Jwt
- I'll double-check the contents internally!
👍 1
m
Hey @Radek Novacek, regarding the non-JWT approach, we looked at your example code (thanks!) and see that it requires 3rd party cookies which are disabled by default in chrome and eventually gonna be phased out. For that reason we’re not sure if that method is gonna work. Regarding: “For the test done with the idToken, we are getting
InvalidBearerTokenException: Token is not a valid Jwt
- I’ll double-check the contents internally!” Daniel is wondering if there are any updates around that. Thanks!
Silent login to dashboard pages is a minor selling point of our product (launching next week-ish now) so your continued assistance is much appreciated!
If you think things might resolve faster with a virtual meeting, let us know and maybe we can try to arrange this, maybe 4PM your time tomorrow?
r
Heya @Michael Gray, you're right about the 3rd party cookies, although this is fixed by using the same domain for the parent application and GoodData Cloud (mentioned here in the documentation, this is a general silent login issue, so not React-specific). On the JWT approach, I'm trying to check with the developer that worked on the JWT implementation for the Cloud - I will ask him if he'd be available to hop on a call with us at 4PM, although it might be a little last minute, so I'll keep you updated on that (and the ID token check) 🙂.
Okay, so as per developer feedback, the thing that we're missing in the idToken is actually in the header - GoodData recognizes a JWT token by two things, the "kid" that pairs it to a JWK, and the "typ"="jwt" header that confirms we are indeed dealing with a JWT. If either of the two is missing, GD doesn't recognize the token as a JWT, and that's where the whole issue starts. Cognito seems to use "token_use" for their type claim instead, and on a quick look, there seem to be ways to modify the token before it gets generated (Lambda triggers), but I'm not sure this can apply to the token header as well..