nginxoauth-2.0azure-active-directory

Get AD security groups in NGINX through OAuth2


I'm currently working on a project with multiple interconnected HTTP APIs and I'm adding authentication. The infrastructure I'm working with has a Azure Active Directory 2025 and an SSO accessed through OAuth2.

I'm trying to simplify Authentication between my HTTP APIs so here's what I've come up with:

All Request between applications have an HTTP header listing security groups in the form of

X-SecGroup: groupone,grouptwo,..."

They use it to authorize their request internally and I won't get into this.

I have an NGINX reverse proxy forwarding requests to those applications. All comminucations go through that proxy. I would like that proxy to get a user request containing the user's OAuth2 token, authorize it through OAuth and somehow use it to contact the AD and get the user's security groups and add that X-SecGroup header to every request it sends downstream.

Is there some way to do that?

If getting the group is impossible and the only way is to check against a list of known security groups, it's okey too :)

Feel free to explain how I'm completely off the mark if I am, I'm still a noob in CyberSec...


Solution

  • Ideally in OAuth you should issue claims like the user's groups to access tokens so that they are readily available to APIs to use for authorization:

    ENTRA ID

    Entra ID requires vendor specific logic that adds complexity. If you try to issue group claims, I believe that Entra ID returns group object IDs as explained in this stack overflow answer.

    To get group names you may need to send an access token to the Graph endpoint at https://graph.microsoft.com/v1.0/groups. You then get a response with items such as those listed below:

    {
      "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#groups",
      "value": [
        {
          "id": "ae0796f2-0b56-4511-8bd7-f86dedf6e2d7",
          "createdDateTime": "2025-07-22T07:26:56Z",
          "description": "Members of the DevOps team",
          "displayName": "devops"
        }
      ]
    }
    

    It is a little tricky though, since Graph requires its own type of access token, whereas you usually need a different access token for your own APIs. To get a Graph access token you are likely to need to use the On Behalf Of Flow. The following Node.js code demonstrates the approach, where an API swaps the incoming access token for a new one:

    const formData = new URLSearchParams();
    formData.append('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer');
    formData.append('client_id', this.configuration.graphClient.clientId);
    formData.append('client_secret', this.configuration.graphClient.clientSecret);
    formData.append('assertion', accessToken);
    formData.append('scope', 'openid profile');
    formData.append('requested_token_use', 'on_behalf_of');
    
    const options = {
        url: this.configuration.tokenEndpoint,
        method: 'POST',
        data: formData,
        headers: {
            'content-type': 'application/x-www-form-urlencoded',
            'accept': 'application/json',
        },
    };
    
    const response = await axios.request(options);
    const graphAccessToken = response.data.access_token;
    

    Finally, you need an app registration in Entra ID to represent the component that makes the Graph API groups request. That entry needs the GroupMember.Read.All permission and also provides the client ID and secret for the above OBO request.

    enter image description here

    SUMMARY

    I suggest that you experiment to get the groups from Entra ID and then decide where to put the code. In NGINX this would be a subrequest which can be tricky to implement. I would try to avoid sending a secure value like groups in plain HTTP headers, since it allows an exploit to send its own groups. Instead, aim for APIs to receive the groups either in the access token (preferred) or by calling the Graph endpoint.