Skip to content

Commit

Permalink
docs(auth): Add a section in OpenID Connect page about MS Entra ID
Browse files Browse the repository at this point in the history
  • Loading branch information
grgrzybek authored and tadayosi committed Mar 5, 2024
1 parent f0c3eff commit 6d464d1
Show file tree
Hide file tree
Showing 13 changed files with 198 additions and 0 deletions.
Binary file added modules/ROOT/images/entra-create-app.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added modules/ROOT/images/entra-endpoints.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added modules/ROOT/images/entra-permissions.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added modules/ROOT/images/entra-platforms.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added modules/ROOT/images/entra-roles.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added modules/ROOT/images/entra-scope.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added modules/ROOT/images/entra-spa-definition.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added modules/ROOT/images/entra-spa.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added modules/ROOT/images/entra-token-configuration.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added modules/ROOT/images/entra-token-groups.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added modules/ROOT/images/entra-user-roles.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
198 changes: 198 additions & 0 deletions modules/ROOT/pages/oidc.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,201 @@ Knowing the structure of JWT access token we can check if role _path_ is configu
# example for Keycloak with use-resource-role-mappings=false
oidc.rolesPath = realm_access.roles
----

== Using Hawtio and OpenID Connect authentication with Microsoft Entra ID

Hawtio 4 has also been tested with https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id[Microsoft Entra ID].

While in theory, everything that should be required to use _any_ OpenID Connect provider is to get access to relevant https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata[OpenID Provider Metadata], in practice we need some provider-specific configuration.

_Clients_ are registered in Entra ID using "App registrations" _blade_. When registering an application, the most important decision is about a _platform_ kind of the Redirect URI:

image::entra-create-app.png[]

There are 2 options to choose from (we're not considering "Public client/native (mobile & desktop)" platform). This UI is presented when configuring Redirect URIs later:

image::entra-platforms.png[]

While it's not obvious what to choose at first glance, it is enough to state:

Web platform:: This kind of client is suitable for server-side applications and APIs.

SPA platform:: SPA applications are running within a browser where it's natural to use "Authorization Code Flow" and so-called _public client_. The reason is that there's no good way of storing credentials and secrets in browser application.

Choosing SPA platform gives us this mark in Entra ID UI:

image::entra-spa.png[]

=== Using single SPA client in Entra ID

After configuring the SPA client in Entra ID, we can already set relevant options in `hawtio-oidc.properties`. At "App registrations" blade in Entra ID we can click "Endpoints" tab and be presented with:

image::entra-endpoints.png[]

Tenant IDs are UUIDs specific to the Entra ID / Azure tenant being used.

Here's the Hawtio configuration where `provider` is the base URL of your tenant and client_id is "Application (client) ID" from the Overview of App Registration page.

----
# OpenID Connect configuration requred at client side
# URL of OpenID Connect Provider - the URL after which ".well-known/openid-configuration" can be appended for
# discovery purposes
provider = https://login.microsoftonline.com/00000000-1111-2222-3333-444444444444/v2.0
# OpenID client identifier
client_id = 55555555-6666-7777-8888-999999999999
# response mode according to https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html
response_mode = fragment
# scope to request when performing OpenID authentication. MUST include "openid" and required permissions
scope = openid email profile
# redirect URI after OpenID authentication - must also be configured at provider side
redirect_uri = http://localhost:8080/hawtio
# challenge method according to https://datatracker.ietf.org/doc/html/rfc7636
code_challenge_method = S256
# prompt hint according to https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
prompt = login
----

The problem with such configuration (where `openid email profile` is sent as a `scope` parameter) is that the assumed scope is in fact `email openid profile User.Read` and the granted access token is (showing only relevant JWT claims):

[,json]
----
{
"aud": "00000003-0000-0000-c000-000000000000",
"iss": "https://sts.windows.net/8fd8ed3d-c739-410f-83ab-ac2228fa6bbf/",
...
"app_displayname": "hawtio",
...
"scp": "email openid profile User.Read",
...
}
----

The `aud` (_audience_) claim is `00000003-0000-0000-c000-000000000000` which is an OAuth2 Client ID of ... https://learn.microsoft.com/en-us/graph/use-the-api[Microsoft Graph API].

Not only such access token shouldn't be used by Hawtio server (with Jolokia agent), also the signature is created using keys associated with Microsoft Graph API.

In order to properly configure Entra ID and ensure that the access tokens generated are _consumable_ by Hawtio Server, we need _two app registrations_ - both for Hawtio Client and Hawtio Server. See the following subchapter.

=== Using SPA together with Web client in Entra ID

What is recommended is to set up _two_ app registrations in Entra ID:

* An SPA client for Hawtio Client application - this is the way to configure an OAuth2 _public client_ with https://datatracker.ietf.org/doc/html/rfc7636[PKCE] enabled.
* A Web (API) client for Hawtio Server application (in fact, its Jolokia API) - this is the Entra ID which exposes an API represented as _scope_ named (for example) `api://hawtio-server/Jolokia.Access`, which is then configured in the above Hawtio Client application as permitted API.

Finally, when the https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth[Authorization Code Flow] is initiated one of the requested scopes in the `scope` parameter is the scope defined for Hawtio Server application (like `api://hawtio-server/Jolokia.Access`).

Let's summarize the configuration required in Entra ID.

1. Create `hawtio-server` app registration with "Web" Redirect URI
2. In "Expose an API" section, add a scope representing the access scope that may be requested from Hawtio Client:
+
image::entra-scope.png[]
+
This will create a reference'able `api://hawtio-server/Jolokia.Access` scope we will use later.
3. In "App roles" section for `hawtio-server` define any roles you want to assign to users within the scope of this client, for example:
+
image::entra-roles.png[]
+
4. In "Enterprise Applications" blade for `hawtio-server` go to "Users and groups" tab and add user-role assignment. For example:
+
image::entra-user-roles.png[]
+
5. Create `hawtio-client` app registration with "SPA" Redirect URI
+
image::entra-spa-definition.png[]
+
6. In "API Permissions" section for `hawtio-client` app registration, add a _delegated permission_ for `hawtio-server` exposed API:
+
image::entra-delegated-permission.png[]
+
This should configure a set of delegated permissions similar to:
+
image::entra-permissions.png[]
+
NOTE: Read more about delegated permissions in https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/delegate-app-roles[Microsoft Entra ID documentation].
7. No User-Role mapping is required for `hawtio-client` in Enterprise Application blade.

Having the above configured, we can properly set the `scope` parameter in Hawtio configuration:

----
# scope to request when performing OpenID authentication. MUST include "openid" and required permissions
scope = openid email profile api://hawtio-server/Jolokia.Access
----

=== Access token configuration

The final, but very important configuration item is the Token Configuration. For `hawtio-server` app registration, which is the app that represents Hawtio Server (and is the component that _consumes_ granted access token) we have to ensure that `groups` claim is added to access token.

Here's the minimal configuration:

image::entra-token-configuration.png[]

`groups` claim need to include _security groups_ and _directory roles_ and groups needs to be represented by names, not UUIDs:

image::entra-token-groups.png[]

For reference, here's the relevant JSON snippet of `hawtio-server` app registration's Manifest:

[,json]
----
"optionalClaims": {
"idToken": [
{
"name": "groups",
"source": null,
"essential": false,
"additionalProperties": []
}
],
"accessToken": [
{
"name": "groups",
"source": null,
"essential": false,
"additionalProperties": [
"sam_account_name"
]
},
...
----

Now the granted access token is no longer specific for Microsft Graph API _audience_. It is intended for `hawtio-server` - `aud` claim is the UUID of `hawtio-server` app registration and `appid` claim is the UUID of `hawtio-client` app registration:

[,json]
----
{
"aud": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"iss": "https://sts.windows.net/.../",
"iat": 1709626257,
"nbf": 1709626257,
"exp": 1709630939,
...
"appid": "55555555-6666-7777-8888-999999999999",
...
"groups": [
...
],
...
"name": "hawtio-viewer",
...
"roles": [
"Hawtio.User"
],
"scp": "Jolokia.Access",
----

The roles which are then transformed (possibly with mapping) are available at `roles` claim and this is reflected in the configuration:

----
# a path for an array of roles found in JWT payload. Property placeholders can be used for parameterized parts
# of the path (like for Keycloak) - but only for properties from this particular file
# example for properly configured Entra ID token
#oidc.rolesPath = roles
...
# properties for role mapping. Each property with "roleMapping." prefix is used to map an original role
# from JWT token (found at ${oidc.rolesPath}) to a role used by the application
roleMapping.Hawtio.User = user
...
----

0 comments on commit 6d464d1

Please sign in to comment.