In a template action I am implementing I need to retrieve a OnBehalfOf token from Azure (https://learn.microsoft.com/en-us/dotnet/api/azure.identity.onbehalfofcredential?view=azure-dotnet) to be able to communicate with a 3rd party API
to do so I need the current user (the user that triggered the action) access token. I am also using Azure for authentication in backstage
#Getting current user accessToken in a custom action
39 messages · Page 1 of 1 (latest)
I tried using ctx.secrets?.backstageToken but after looking at it with jwt.io I can see it's a ES256 token and I think azure is expecting a RS256 token so I am getting this error
AuthenticationRequiredError: invalid_request: 5002730 - [2024-01-23 21:24:08Z]: AADSTS5002730: Invalid JWT token. Unsupported key for the signing algorithm. Trace ID: xxxxxxxxx Correlation ID: xxxxxxxxx Timestamp: 2024-01-23 21:24:08Z - Correlation ID: xxxxxxxxx - Trace ID: xxxxxxxxx
I guess what I really need is the original oauth token from azure that was used at login time (similare to this question https://discord.com/channels/687207715902193673/1140616003827544115)
Discord
Discord is the easiest way to communicate over voice, video, and text. Chat, hang out, and stay close with your friends and communities.
the canonical way of doing things like this is to explicitly leverage eg microsoftAuthApiRef to get an access token that you send along as a secret
the tokens involved in the initial login exchange aren't necessarily directly available
do you have an example of this (even with auth another provider)
i think @fiery marsh knows more about what to best point to here
whether a custom field extension is needed etc https://backstage.io/docs/features/software-templates/writing-custom-field-extensions
which would produce a secret that then could be consumed similarly to what's documented here https://backstage.io/docs/features/software-templates/writing-templates#using-secrets
so with this approach the end user would have to re-enter his user/password as part of the template action form ?
@vital stump no, the idea is that the field extension will use the microsoftAuthApiRef to get a token, and if they're already authenticated they won't get a username and password popup. You don't have to render any UI component of course, it can just be a hidden component that mounts in the form that collects this secret and adds it to secrets using setSecret from useTemplateSecrets hook
We do something similar already in the RepoUrlPicker
That could be an option for you to use if you're looking for permissions for a repository
ok so this could be a new field type that I have to implement
would I be able to hide it in the scaffolder form ? I don't want the user to enter anything additional, just grab his oauth token
Yeah you can do that for sure. You can just add it to the parameters block and use ui:field to reference the new field that you've created
That's gonna render it in the form, but nothing is required.
You can of course also use the RepoUrlPicker if you want to collect repo information, it will automatically decorate a token using the microsoftAuthApiRef I think. But if you just want the token and no input from the user, then you're better to write you own.
i'll try this out, thanks for the input
ok I think I am quite close
this is my custom field
export const MicrosotOauthTokenProvider = ({
name,
}: FieldProps<string>) => {
const microsoftAuthApi= useApi(microsoftAuthApiRef);
const { setSecrets, secrets } = useTemplateSecrets();
useDebounce( async() => {
const accessToken = await microsoftAuthApi.getAccessToken();
setSecrets({
[name]: accessToken
});
console.log(secrets);
});
return (<FormControl/>);
};
and I can see the expected access token under the expected key (I use the name of the field) in secrets (in the console log)
from the console log
{msAuthToken: '<My Azure OAuth Token>'}
and then I try to pass up the secret to the action via
...
msAuthToken:
title: Microsoft Auth Token
ui:field: MicrosotOauthTokenProvider
ui:backstage:
review:
show: false # won't print any info about 'hidden' property on Review Step
...
steps:
- id: scm-create-project
name: Create the project in SCM
action: scm-create-project
input:
...
accessToken: ${{ secrets.msAuthToken }}
but when I submit the form I get a bad request InputError: Invalid input passed to action scm-create-project, instance requires property "accessToken"
If I replace {{ secrets.msAuthToken }} with a static string like 'foo' the info is correctly passed along and I don't get that invalid input error
and I can see the in the payload to POST to api/scaffolder/v2/dry-run template.secrets is indeed empty
@fiery marsh
with this code
export const MicrosotOauthTokenProvider = ({
name,
}: FieldProps<string>) => {
const microsoftAuthApi= useApi(microsoftAuthApiRef);
const { setSecrets, secrets } = useTemplateSecrets();
useDebounce( async() => {
const accessToken = await microsoftAuthApi.getAccessToken();
setSecrets({
[name]: accessToken
});
console.log(secrets);
});
return (<FormControl/>);
};
the token I retrieve has an audience
"aud": "00000003-0000-0000-c000-000000000000",
00000003-0000-0000-c000-000000000000 is Microsoft Graph (https://learn.microsoft.com/en-us/troubleshoot/azure/active-directory/verify-first-party-apps-sign-in)
I would have expected "aud": "<backstage's client ID>"
am I using the wrong apiRef to fetch the access token ?
i see that I get a token with the appropriate aud if I specify
const accessToken = await microsoftAuthApi.getAccessToken('api://<backstage_client_id>/user_impersonation');
however im trying to retrieve <backstage_client_id> via configApiRef
const config = useApi(configApiRef);
const authConfig = config.getConfig('auth');
const authEnv = authConfig.getString('environment');
const msAuthProviderConfig = authConfig.getConfig('providers').getConfig('microsoft').getConfig(authEnv)
but authConfig.getConfig('providers').getConfig('microsoft') is empty
the same code on the backend returns the expected config
visibility should be frontend according to this https://github.com/backstage/backstage/blob/2bd94c454e82c955450cb791a8f54c0da7adc7af/plugins/auth-backend-module-microsoft-provider/config.d.ts#L21
oh I see
The visibility only applies to the direct parent of where the keyword is placed in the schema. For example, if you set the visibility to frontend for a subset of the schema with type: "object", but none of the descendants, only an empty object will be available in the frontend. The full ancestry does not need to have correctly defined visibilities however, so it is enough to only for example declare the visibility of a leaf node of type: "string".
so how should I go about to retrieve my backstage clientId at the frontend level ?
You can never under any circumstance access config in the frontend that's not explicitly marked with frontend visibility
It's just not possible but design for security reasons
And indeed the visibility does not propagate to children
The microsoftAuthApiRef is different; it let's you negotiate an oauth access token that can be used in requests to do things on your behalf
That is done with the assistance of the auth backend
yes but when I am calling await microsoftAuthApi.getAccessToken() without specifying a scope I am getting a token with the "aud": "00000003-0000-0000-c000-000000000000" which is Microsoft Graph
I would like to get a token with "aud": "<backstage client id>"
from the same microsoftAuthApi if do getIdToken() I get an id token with "aud": "<backstage client id>"
This sounds like it's somewhat related to https://github.com/backstage/backstage/issues/22644 - when you request scopes, you'll need to fully qualify the scopes (e.g. <backstage client id>/.default), or we'll need to update the microsoftAuthApi to allow you to explicitly specify the resource to use.
but here im stuck then because I need the <backstage client id> as scope but I cannot retrieve it at the from the config at the frontend because of the @visibility of that config element
ClientId should be visible on the front end - it's only the clientSecret that's marked as secret.
https://github.com/backstage/backstage/blob/36dbf7e6a161b8d7320e4ebc521cd00e58e1b79d/plugins/auth-backend-module-microsoft-provider/config.d.ts#L21
Got to be explicitly marked frontend unfortunately