bawsky
bawsky•2d 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
1 Reply
bawsky
bawskyOP•2d 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)

Did you find this page helpful?