A rambling post on Issuer Claim, v1.0 and v2.0 endpoint, Access Tokens, etc.

Just a post to myself on the Issuer (iss) claim in Azure AD tokens. Got confused today with these and this is an attempt to make note of them in one place.

I am talking about Azure AD tokens in the context of authentication to Azure Functions via Azure AD. There are numerous posts on that topic in this blog. In this first post I examined an ID token and its iss claim is “https://login.microsoftonline.com/<tenantId>/v2.0“.

Unfortunately I didn’t take a screenshot of what is setup on the Azure Function side, but looking at the docs today it looks like the Issuer must be similar to the above – i.e. “https://login.microsoftonline.com/<tenantId>/v2.0“. The only difference being to omit “/v2.0” in case of Azure AD v1 endpoints.

Am guessing that’s what I had as standard on my Azure Function too, but I am not sure*. Because in a later post where I authenticate to the Function via client credentials grant, I have a screenshot where on the Azure Function side I have “https://sts.windows.net/<tenantId>/v2.0” instead. Moreover, in that instance the token I was examining had the iss claim as “https://sts.windows.net/<tenantId>/” and I had even changed it on Azure Functions to remove “/v2.0“.

(*Actually, scratch that. While writing this post I created a new Azure Function and set it up to use Azure AD. The Issuer Url of that is “https://sts.windows.net/<tenantId>/v2.0” – so the MS doc is incorrect).

What is the difference between these two URLs btw? The “sts.windows.netiss claim is returned when using a v1.0 endpoint; the “login.microsoftonline.com” endpoint is used with a v2.0 endpoint. I talk about this in a prior blog post too. From this GitHub thread that I linked there too:

From another doc, on App Registrations, the accessTokenAcceptedVersion property in the App Registration manifest “specifies the access token version expected by the resource” (so… Azure Functions, in my case?). To quote further: “The endpoint used, v1.0 or v2.0, is chosen by the client and only impacts the version of id_tokens.

Possible values for this property are 1, 2, or null. 2 corresponds to a v2.0 endpoint; while 1 and null correspond to a v1.0 endpoint. By default the value is null, so the App Registration returns v1.0 tokens I suppose? I say “I suppose” because if I try to get an ID token via the ROPC or Device Code flow I always get the Issuer as “login.microsoftonline.com” and not “sts.windows.net” – which makes no sense. Interestingly, in both flows the Access Token I get have the Issuer as “sts.windows.net”.

This leads me to think I must have got my understanding of the accessTokenAcceptedVersion property wrong.

I learnt that one can figure out the version of the endpoint from the “ver” claim in the token too. Based on this it looks like ID tokens are v2.0 and hence use “login.microsoftonline.com” while access tokens are v1.0. So maybe the document is incorrect in saying that the accessTokenAcceptedVersion property only affects ID tokens; instead it affects Access Tokens just like its name says.

Moving on…

I am pretty sure that even with the Issuer being “login.microsoftonline.com” in the ID tokens I get, and Azure Functions set to “sts.windows.net“, it has worked so far for me. The only time it didn’t work was when I used the Client Credentials grant flow as mentioned earlier because of the extra “/v2.0” in the Azure Function Issuer URL that the Access Token did not have – and that makes sense because this flow returns Access Tokens, as like I noted with the other two flows above too Access Tokens seem to have issuer as “sts.windows.net”.

To recap:

  • By default Azure Function + Azure AD has an Issuer URL set to “https://sts.windows.net/<tenantId>/v2.0
  • ROPC and Device Code flow ID tokens have the Issuer URL claim set to “https://login.microsoftonline.com/<tenantId>/v2.0“. The same in Access tokens is “https://sts.windows.net/<tenantId>/
  • Client Credential flow doesn’t return ID tokens so Issuer URL is moot there; it returns Access tokens and they too have “https://sts.windows.net/<tenantId>/
  • To get Client Credential flow working I have to change the Issuer URL from “https://sts.windows.net/<tenantId>/v2.0” to “https://sts.windows.net/<tenantId>/” on the Azure Function side.
  • I am not sure what Issuer URL is present in the claims when a Logic App calls an Azure Function via Azure AD authentication – Access tokens, if I had to guess – but it works fine with either “sts.windows.net” variant on the Azure Function side.

Here’s something interesting. While the Azure Function had its Issuer URL set to “https://sts.windows.net/<tenantId>/v2.0” I was able to authenticate against it via the ROPC and Device Code flows even though their issuer URL was “https://login.microsoftonline.com/<tenantId>/v2.0“. When I changed the Issuer URL (in Azure Functions) to remove the “/v2.0”, that broke the ROPC and Device Code flows.

That led me to experimenting with the Issuer URL on the Azure Function side by changing it from “https://sts.windows.net/<tenantId>/” to “https://login.microsoftonline.com/<tenantId>/" and testing the Client Credential flow. I expected it to fail, but it worked fine. Adding a “/v2.0” back broke it again, though.

To summarize:

  • https://sts.windows.net/<tenantId>/v2.0” and “https://login.microsoftonline.com/<tenantId>/v2.0” seem to be equivalent.
  • Issuer URL of a newly created Azure Function setup with Azure AD authentication is “https://sts.windows.net/<tenantId>/v2.0
  • https://sts.windows.net/<tenantId>/” and “https://login.microsoftonline.com/<tenantId>/” seem to be equivalent.
  • What seems to matter is the “/v2.0” – ROPC and Device Code flow ID tokens have this as Issuer; Client Credential flow Access tokens don’t.
  • A Logic App connecting to Azure Function using via Azure AD authentication (using a Managed Identity) seems to be ok either ways.

So what do I do if I want to open up authenticated Access to an Azure Function and want all three flows to work?

To begin with, using the “/v2.0” Issuer seems to be the sensible idea – assuming it means we are using the v2 endpoint. So I should add that back on the Azure Function side (which is the default anyways) and look to what I can do so that the Client Credential flow access token too is from a v2.0 endpoint.

I know that the accessTokenAcceptedVersion property can be used to get a token from the v2.0 endpoint. It seems to be for ID tokens only, while I am interested in Access tokens (as its a Client Credential flow) but assuming the doc is wrong it’s worth changing this to see if I can get Access Tokens from the v2.0 endpoint. This way I can use the “/v2.0” Issuer URL with all three flows.

When I use Client Credentials I am doing what I blogged about in a prior post. I have App Registrations for each “client”. They have permissions (via App Roles) to the App Registration tied to the Azure Function. So what I should probably do is change the accessTokenAcceptedVersion property on the App Registration tied to the Azure Function? Not sure, but that’s what I am going to try. Weird thing is this is what the Logic App Managed Identity too has permissions to (via App Roles), and it works already, so I hope it doesn’t break anything.

The change took effect instantly. And voila! whereas previously I was getting an Access Token like this:

Now I get this:

The new token is indeed v2.0 (“ver” claim). And it has the “/v2.0” Issuer URL (“iss” claim). Slight differences – in the v1.0 token the audience (“aud” claim) was of the form “api://<appId>” whereas now its just “<appId>” (this <appId> is that of the one tied to the Azure Function by the way). And more importantly, previously the App Id of the App Registration itself (that of the client who is doing the Client Credential flow) was the “appid” claim but now its “azp“. This is confirmed by an MS doc too that says “azp” is a replacement for “appid” and only present in v2.0 tokens while “appid” is only in v1.0 tokens.

The Logic App continues to work as before, so no unexpected issues there either.

So, to re-summarize:

  • https://sts.windows.net/<tenantId>/v2.0” and “https://login.microsoftonline.com/<tenantId>/v2.0” seem to be equivalent Issuer URL endpoints.
  • https://sts.windows.net/<tenantId>/” and “https://login.microsoftonline.com/<tenantId>/” seem to be equivalent.
  • What seems to matter is the “/v2.0” – ROPC and Device Code flow ID tokens have this as Issuer; Client Credential flow Access tokens don’t. This is because ID tokens seem to default to the v2.0 endpoint while Access tokens default to the v1.0 endpoint (so the type of token is what seems to change things, rather than ROPC/ Device Code vs Client Credentials; Access Tokens got via the former flow are v1.0 instead of v2.0)
  • A Logic App connecting to Azure Function using via Azure AD authentication (using a Managed Identity) seems to be ok either ways. I think that’s because Managed Identities don’t use tokens; on the Function side I don’t see any tokens when authenticating with the Managed Identity of a Logic App, instead the headers have ‘x-ms-workflow-name‘ (only for Logic Apps) and ‘x-ms-client-principal-id‘ (for both Logic Apps and if I were to invoke one Azure Function from another via Managed Identity) values which gives the impression its some other mechanism.
  • Issuer URL of a newly created Azure Function setup with Azure AD authentication is “https://sts.windows.net/<tenantId>/v2.0“. It’s fine to leave it as is – but you got to take care if using Client Credential flow (see next point).
  • The App Registration against which the “client” using Client Credential flow authenticates must have its accessTokenAcceptedVersion property in the manifest set to 2. This will result in the Access Token using the v2.0 endpoint. In general this is what one needs to do if Access Tokens must be got from the v2.0 endpoint (as ID tokens seem to default to v2.0 anyways).

And that’s all!

This post/ investigation began as an itch I wanted to scratch as I was getting a bit over my head when making some changes yesterday. I sat up way past my usual bedtime trying to figure this out yesterday; and now its way past bedtime of the next day and I have finally cracked it. Yay!

Lastly, here’s an excerpt from some code I have been developing through these series of posts of authenticating to Azure Functions. I keep modifying it as I discover new edge cases, and variations of this are in other posts as snippets.