bawsky
bawskyβ€’4mo ago

Access the authorize request in the Complement Token flow (Actions V1)

Hello Zitadel crew πŸ‘‹ We have a need to pass arbitrary parameters in the /authorize request and then run some API request inside an action using those parameters, to finally complement the token claims. E.g.: 1- User attempts to login and the frontend passes the arbitrary company_id=42 query parameter in the /authorize request 2- On an action inside Zitadel, we read that arbitrary parameter and use that to make an internal request to our backend API (to e.g.: check if user really has access to company_id=42 3- If response of that internal request is successful, the Zitadel action complements the access token with a new claim (e.g.: "company_id": 42) Looking through the documentation I didn't find a way of doing that using Action V1 - is it possible? Note: we're on Zitadel 2.67.3 (default version from Helm charts) and using only Actions V1 for now
5 Replies
bawsky
bawskyOPβ€’4mo ago
I was able to workaround that by using two separate actions (one in a Post Authentication, and one in Complement Token) - in the authorize request, pass state encoded with the arbitrary data I want, e.g.: state=company_id:42;subdomain:mycompany;foo:bar - in an action in External Authentication or Internal Authentication flow, parse the state (from ctx.v1.authRequest.transferState), then add company_id to the user metadata, e.g.:
let stateTuples = ctx.v1.authRequest.transferState.split(";").map((str) => str.split(":"));
let companyIdTuple = stateTuples.find((tuple) => tuple[0] === "company_id");
if (companyIdTuple && companyIdTuple[1]) {
api.v1.user.appendMetadata("company_id", companyIdTuple[1]);
}
let stateTuples = ctx.v1.authRequest.transferState.split(";").map((str) => str.split(":"));
let companyIdTuple = stateTuples.find((tuple) => tuple[0] === "company_id");
if (companyIdTuple && companyIdTuple[1]) {
api.v1.user.appendMetadata("company_id", companyIdTuple[1]);
}
- in an action in Complement Token flow (pre access token creation):
// no error handling for brevity
const user = ctx.v1.getUser();
const metadata = ctx.v1.user.getMetadata();
const companyIdMetadata = metadata.metadata.find(m => m.key === 'company_id');
const companyId = Number(companyIdMetadata.value);
api.v1.claims.setClaim('company_id', companyId);
// no error handling for brevity
const user = ctx.v1.getUser();
const metadata = ctx.v1.user.getMetadata();
const companyIdMetadata = metadata.metadata.find(m => m.key === 'company_id');
const companyId = Number(companyIdMetadata.value);
api.v1.claims.setClaim('company_id', companyId);
This is not great though... If the user is logged into multiple devices the state could leak between them, e.g.: - login using my computer passing company_id=1 - and that is successfuly added as a claim in the token - login using my phone passing company_id=42 - when my computer generates a new token (using the refresh_token), it would NOT go through the Post Authentication Flow and read directly from the user metadata, so it would add company_id=42 to the new token claims - regardless of the previous company_id my computer used to authenticate So, thinking about it, I guess if I want to pass arbitrary data into my token claims, the correct approach would be passing through the /token request - NOT the /authorize request, right? That way we theoretically wouldn't have to store any "intermediate" state I don't think this is possible in Actions V1 at all πŸ€” Perhaps it is possible in Actions V2? (set custom claims based on parameters sent to /token request)
Jim Morrison
Jim Morrisonβ€’3mo ago
Good morning @bawsky I'm tagging one of our engineers @Rajat to help on this front and share his insights. Thanks for your patience!
bawsky
bawskyOPβ€’3mo ago
Fantastic, thanks a lot @Jim Morrison and @Rajat πŸ˜„ Hope we can get a way to overcome this - it'd be very helpful to have a more flexible way to complement tokens For now the workaround we've took is something on the lines of: - add support for a new custom header, e.g.: x-company-id: 42 - during every request, we validate if the user really have access to that company set on their headers (note: this is a bit tricky in the case of impersonating other users from other companies - something we also support - in impersonation scenarios we have to validate whether the original user has access to impersonate the impersonated user ON <x-company-id>) Instead of that workaround, we'd like it if it was possible to submit a company_id on the /token request and use it to change the structure of our jwts (after validating in our backend via an API call inside actions) to something like:
# normal, non impersonated token:
sub: <user's id>
company_id: <user's company>
# normal, non impersonated token:
sub: <user's id>
company_id: <user's company>
regarding impersonation (via token exchange), ideallly we also would like the possibily to, inside actions, make a request to validate if the user can impersonate some speciific user on some specific company, and if yes, generate an impersonate token like:
sub: <impersonated user's id>
company_id: <impersonated user's company>
act: {
sub: <impersonator's id>
company_id: <impersonator's company id>
}
sub: <impersonated user's id>
company_id: <impersonated user's company>
act: {
sub: <impersonator's id>
company_id: <impersonator's company id>
}
(I must say: I really like token exchange beta feature! hope it gets more love and traction πŸ˜„ )
Rajat
Rajatβ€’3mo ago
hey @bawsky thanks for the context and I will read it and get back to you hey @bawsky with Actions V1 it’s not possible to pass arbitrary params from /authorize into the token claims directly without workaround. Actions V2 supports executing during the Token Complement Flow (pre‑token creation) where you can use setClaim() to inject the company_id (or impersonation id) into the token. You might wanna read about it here.
bawsky
bawskyOPβ€’3mo ago
So is it possible to access arbitrary params/headers in actions V2? More specifically, in Action V2, can I either: - access arbitrary params/headers in /authorize request, then re-access those during the /token request to complement the token - being ensured that the data I'm accessing corresponds to my current OIDC session (i.e.: arbitrary data from an /authorize request can be tied to the current OIDC session, when accessing it in the complement token flow)? - access arbitrary params/headers in /token request? Either would work for me - just want to better understand how Actions V2 can solve the problem Also, just want to confirm in regards to impersonation: Is it correct then that I am able to access act.sub inside the Token Complement flow (or equivalent) in Actions V2? The reason of my surprise is that, from my own testing, in Actions V1 (Zitadel v2.67.2) act.sub (the actor/original user id) is simply this not visible inside the Complement Token flow

Did you find this page helpful?