Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AdditionalFields empty in IDToken #436

Open
dannmartens opened this issue Jun 26, 2023 · 6 comments
Open

AdditionalFields empty in IDToken #436

dannmartens opened this issue Jun 26, 2023 · 6 comments
Labels
enhancement New feature or request

Comments

@dannmartens
Copy link

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?

@bgavrilMS
Copy link
Member

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.

@dannmartens
Copy link
Author

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?

@bgavrilMS
Copy link
Member

@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).

@dannmartens
Copy link
Author

dannmartens commented Aug 29, 2023

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?

@bgavrilMS
Copy link
Member

Yes, there is a RawToken field on the IdToken

image

@bgavrilMS bgavrilMS added the enhancement New feature or request label Aug 30, 2023
@dannmartens
Copy link
Author

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.")
	}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants