AzureAD / microsoft-authentication-library-for-go

The MSAL library for Go is part of the Microsoft identity platform for developers (formerly named Azure AD) v2.0. It enables you to acquire security tokens to call protected APIs. It uses industry standard OAuth2 and OpenID Connect.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

AdditionalFields empty in IDToken

dannmartens opened this issue · comments

When using App Roles in an application, the returned "roles" field in the decoded JWT token cannot be accessed through the returned IDToken struct.

There appears be a possibility to find unmapped fields in:

type IDToken struct {
...
	AdditionalFields map[string]interface{}
}

but this field remains empty, even when the actual token decodes to:

{
  "typ": "JWT",
  "alg": "RS256",
  "kid": "..."
}.{
...
  "roles": [
    "administrator"
  ],
  "sub": "...",
  "tid": "...",
  "ver": "2.0"
}.[Signature]

Is this expected or do I need to invoke the API differently to have "roles" included?

If there are too many roles associated with the user, then AAD will put a link to Graph instead of the list of roles. So we recommend making a Graph call to get the roles for the user, instead of relying on them being in the token.

https://learn.microsoft.com/en-us/graph/api/directoryrole-list?view=graph-rest-1.0&tabs=http

Also, you should not perform AuthZ based on IdToken. The ID Token is for the app (e.g. a console app) to provide an UI experience to the end-user (.e.g. you are logged in with username X). But AuthZ should be done by the protected API based on the access token.

I am bit surprised by your reply, because your employer does not seem to agree... and neither do I.

https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-add-app-roles-in-apps

One of the reasons this feature turned out to be useful, is that a second call to the Graph API degraded the user experience on account of the increased waiting time to authorize the user. Granted, App Roles are less useful for fine-grained authorization purposes, but they are definitely useful for certain use cases.

Are you saying this official library from Microsoft refuses to support this feature?

@dannmartens - I'm offering advice on how to ensure your app is reliable and won't break in the future. I'll get those docs modified, to mention this problem at least.

I agree that Roles and other claims from the id token should be exposed. But it's not a string, it's a JSON array, so I'm not sure additionalFields is the right place for it.

Consider using a library like https://github.com/golang-jwt/jwt to parse the ID Token yourself. You do not need to validate the token, the fact that it comes from AAD is enough (unlike the Access Token, which the web api MUST validate).

I do appreciate the advice! I will read up on the possible reply modes for the roles claim.

I am familiar with similar issues, for example when too many group memberships break things unexpectedly, such as HTTP headers in SPNEGO.

The intended use case only requires a very limited amount of App Roles, as it helps the application to authorize on a high level. For complex authorization scenarios, I would probably not even recommend integrating with features of the Graph API.

I just wanted to avoid having to parse the token myself, after your library already did all the heavy lifting! Do you expose the raw token, or can that exposure be requested?

Yes, there is a RawToken field on the IdToken

image

Since it is already a transitive dependency, I used golang-jwt:

package main

import (
	"fmt"

	"github.com/golang-jwt/jwt/v5"
)

func main() {
	rawToken := "YOUR_RAW_TOKEN_HERE"

	// You don't validate the token in this example since you only want to parse it.
	token, _, err := new(jwt.Parser).ParseUnverified(rawToken, &jwt.MapClaims{})
	if err != nil {
		fmt.Println("Error parsing token:", err)
		return
	}

	if claims, ok := token.Claims.(*jwt.MapClaims); ok {
		if roles, ok := (*claims)["roles"].([]interface{}); ok {
			for _, role := range roles {
				fmt.Println(role)
			}
		} else {
			fmt.Println("No roles found or they are not in the expected format.")
		}
	} else {
		fmt.Println("Invalid token claims.")
	}
}