Get Azure groups information in keycloak id_token - azure-active-directory

I have registered Azure active directory as an identity provider in the Keycloack server.
I am getting groups array in azure's id_token and it looks like:
{
aud: "https://contoso.onmicrosoft.com/scratchservice",
iss: "https://sts.windows.net/b9411234-09af-49c2-b0c3-653adc1f376e/",
iat: 1416968588,
nbf: 1416968588,
exp: 1416972488,
ver: "1.0",
tid: "b9411234-09af-49c2-b0c3-653adc1f376e",
amr: [
"pwd"
],
roles: [
"Admin"
],
oid: "6526e123-0ff9-4fec-ae64-a8d5a77cf287",
upn: "[hidden email]",
unique_name: "[hidden email]",
sub: "yf8C5e_VRkR1egGxJSDt5_olDFay6L5ilBA81hZhQEI",
family_name: "User",
given_name: "Sample",
groups: [
"0e129f6b-6b0a-4944-982d-f776000632af",
"323b13b3-1851-4b94-947f-9a4dacb595f4",
"6e32c250-9b0a-4491-b429-6c60d2ca9a42",
"f3a161a7-9a58-4e8f-9d47-b70022a07424",
"8d4c81b2-b1ad-476d-9574-544d155aa6ff",
"1bf80164-ff24-4866-b19c-6212e5b9a847",
"76f80127-f2cd-46f4-8c52-8edd8bc749b1",
"0ba27160-44d0-42b5-b90c-47b3fcc48e35"
],
appid: "b075ddef-0efa-123b-997b-de1337c29185",
appidacr: "1",
scp: "user_impersonation",
acr: "1"
}
In id_token generated by the Keycloak server, there are no groups.
How can I get azure AD groups in keycloack id_token.

I resolved this issue by creating a mapper.
Steps:
Create Attribute importer type mapper in the identity provider
Create User attribute type mapper in the client

Related

ASP.NET Zero solution for both identityserver and client

Before getting into the issue, let me tell you what I am trying to achieve.
I need to implement sort of SSO in all of my applications. For which I want to use ASP.NET Zero solutions as
SSO Provider as well as Clients.
Is it possible or am I overthinking?
I am using ASP.NET Zero template: ASP.NET Core - MVC & jQuery
I am very new to IdentityServer and OpenId so please excuse for my silly mistakes if I have made.
In one of ABP project, I have added a static client to IdentityServer AppSettings like below.
First project's AppSettings - Hosted application
{
"ClientId": "localhost",
"ClientName": "MVC Client Demo",
"AllowedGrantTypes": [
"implicit"
],
"RequireConsent": "true",
"ClientSecrets": [
{
"Value": "test"
}
],
"RedirectUris": [
"https://localhost:44302/signin-oidc"
],
"PostLogoutRedirectUris": [
"https://localhost:44302/Account/Login"
],
"AllowedScopes": [
"openid",
"profile",
"email",
"phone",
"default-api"
],
"AllowOfflineAccess": "true"
}
Now from my second ABP project (localhost), I am trying to enable OpenId to authenticated through above server.
Second project's AppSettings - Running on localhost
"OpenId": {
"IsEnabled": "true",
"Authority": "https://[applicationname].azurewebsites.net/",
"ClientId": "localhost",
"ClientSecret": "test",
"ValidateIssuer": "true",
"ClaimsMapping": [
{
"claim": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",
"key": "http://schemas.microsoft.com/identity/claims/objectidentifier"
}
]
}
However I am not getting any error, in logs I can see there is a message that says:
AuthenticationScheme: Identity.External signed in.
And a cookie is being created with key "Identity.External" but login-is not happening successfully.
Inside AccountController below line returns null and that resulting into unsuccessful login.
**var externalLoginInfo = await _signInManager.GetExternalLoginInfoAsync();**
if (externalLoginInfo == null)
{
Logger.Warn("Could not get information from external login.");
return RedirectToAction(nameof(Login));
}
Try adding
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
before services.AddAuthentication()
This will map sub claim to NameIdentifier claim so GetExternalLoginInfoAsync will not return null.

How to add service principal as an Owner to the Azure AD group through Graph API

My requirement is to create a group in Azure AD and add a service principal as an owner of that group through Graph API - While creating the group.
Reference Doc:
https://learn.microsoft.com/en-us/graph/api/group-post-groups?view=graph-rest-1.0&tabs=http
With reference to the above document, i am able to create group but the response object shown is not allowing service principal to be added as an Owner.
Body :
{
"description": "Testing 123",
"displayName": "SG-test-ei",
"mailEnabled": false,
"mailNickname": "SGP-test-ei",
"securityEnabled": true,
"owners#odata.bind": [
"https://graph.microsoft.com/v1.0/users/qwbhty-cdd0-4c42-b857-5b8ce0ae6a9e"
],
"members#odata.bind": [
"https://graph.microsoft.com/v1.0/users/7f3f587a-c40a-4a63-82b1-202f35c828ee"
]
}
Seems like, https://graph.microsoft.com/v1.0/users does not recognize ObjectID for a service principal.
My question is, what owners#odata.bind or API i should use in the body to add service principal as an Owner to the security group in Azure AD.
The example in the link uses /users.
POST https://graph.microsoft.com/v1.0/groups
Content-Type: application/json
{
"description": "Group with designated owner and members",
"displayName": "Operations group",
"groupTypes": [
"Unified"
],
"mailEnabled": true,
"mailNickname": "operations2019",
"securityEnabled": false,
"owners#odata.bind": [
"https://graph.microsoft.com/v1.0/users/26be1845-4119-4801-a799-aea79d09f1a2"
],
"members#odata.bind": [
"https://graph.microsoft.com/v1.0/users/ff7cb387-6688-423c-8188-3da9532a73cc",
"https://graph.microsoft.com/v1.0/users/69456242-0067-49d3-ba96-9de6f2728e14"
]
}
Replace this with /servicePrincipals like bellow:
"owners#odata.bind": [
"https://graph.microsoft.com/v1.0/servicePrincipals/00964c82-a7c2-4675-bbed-54bcf16328b3"
]

Blazor Authentication with Azure AD - Retrieving User Roles

I've successfully setup a Blazor application to authenticate with the Azure tenant where I work. The authentication works beautifully. I have the App Registration setup in Azure with appRoles defined in the manifest. I've add a few users to the application with those roles assigned however I'm not getting any Role claims back on the user context after it authenticates.
Startup.cs
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
Manifest:
"appRoles": [
{
"allowedMemberTypes": [
"User"
],
"description": "Coming soon.",
"displayName": "Viewer",
"id": "{guid goes here}",
"isEnabled": true,
"lang": null,
"origin": "Application",
"value": "Viewer"
},
{
"allowedMemberTypes": [
"User"
],
"description": "Coming soon.",
"displayName": "Manager",
"id": "{guid goes here}",
"isEnabled": true,
"lang": null,
"origin": "Application",
"value": "Manager"
}
],
I'm trying to retrieve those roles with the claims after authentication, but no roles are coming through. This is obviously making IsInRole not work and for the life of me I can't find any samples of code to achieve this.
I would greatly appreciate it if someone could point me in the right direction!
If the user has been assigned application roles, it should be returned in the id token. You can decode the id token by using https://jwt.io/.
I didn't find a blazor sample, but you can refer to this aspnetcore sample.

create a application role using Microsoft Graph API

I have created app registration in azure aad. I want to add a app role using Microsoft Graph API programmatic.
It seems that there is no Microsoft Graph API to do that. If Azure AD graph is acceptable, you use the following rest API to do that.
PATCH https://graph.windows.net/{tenantId}/directoryObjects/{objectId}/Microsoft.DirectoryServices.Application?api-version=1.6
Note: objectId not applicationId, we could get it from Azure portal.
The following is the test body
appRoles": [
{
"allowedMemberTypes": [
"User"
],
"displayName": "SurveyAdmin",
"id": "c20e145e-5459-4a6c-a074-b942bbd4cfe1",
"isEnabled": true,
"description": "Administrators can manage the Surveys in their tenant",
"value": "SurveyAdmin"
}
]
Test result:
We also could check it in the application manifest from Azure portal.
According to my experience, I added appRoles with the help of MicrosoftGraphAPI. You can use this:
respond = "https://graph.microsoft.com/v1.0/applications/{apps-Object-ID}"
request_body = json.dumps({
"appRoles": [ {
"allowedMemberTypes": ['User'],
'description': 'access',
'displayName': 'access',
'id': "XXXX-XXXX-XXX",
"isEnabled": "true",
"value": "null",
"origin": "Application"
}
]
})
response = requests.patch(respond, headers, request_body)
And don't forget to import json and requests

Microsoft Azure AD graph API: How do I retrieve a user's email address?

I am able to get access to a user's accessToken, and am making a call to GET https://graph.microsoft.com/v1.0/me with an Authorization: Bearer <token> header.
However, in the response body I'm getting something like this:
{
"#odata.context": "https://graph.microsoft.com/v1.0/$metadata#users",
"value": [
{
"givenName": "Foo",
"surname": "Bar",
"displayName": "Foo Bar",
"id": "b41efha115adcca29",
"userPrincipalName": "email.address#outlook.com",
"businessPhones": [],
"jobTitle": null,
"mail": null,
"mobilePhone": null,
"officeLocation": null,
"preferredLanguage": null
}
]
}
The mail property is null, and the userPrincipalName in this response body happens to the be the user's email address. However, there's this from Microsoft's docs:
Although the UPN and email share the same format, the value of the UPN for a user might or might not be the same as the email address of the user.
When initiating the login request of the user, we're requesting for the "user.read" and "email" scopes. We're using the MSAL.js library to obtain the access token, and our code reads something like this:
login (): ng.IPromise<IMicrosoftOAuthResponse> {
const prom = this.userAgentApplication.loginPopup(["user.read", "email"])
.then((idToken) => {
return this.userAgentApplication.acquireTokenSilent(["user.read", "email"]);
})
.then((accessToken) => {
// at this point, we have the accessToken and make a call to the graph api
});
return this.$q.when(prom);
}
How do I get the actual email address of the user here?
The mail property is set in one of 2 ways:
It's been set on on-premises AD, and then synchronized to Azure AD using AD Connect
The cloud user has been assigned an Office 365 license (and a mailbox), at which point the mail property is set for this licensed user.
If the user does not have an O365 mailbox/license, you could also search for the user by userPrincipalName, displayName, etc. $filter supports the OR operator.
Hope this helps,
Even though this is an old question, I thought I would share my solution to getting the email of a signed-in user. Be aware that this solution requires access to the user's id_token.
The response from calling the /me endpoint looks as follows:
Object {
"#odata.context": "https://graph.microsoft.com/v1.0/$metadata#users/$entity",
"businessPhones": Array [],
"displayName": "John Doe",
"givenName": "John",
"id": "xxxxxx",
"jobTitle": null,
"mail": null,
"mobilePhone": null,
"officeLocation": null,
"preferredLanguage": null,
"surname": "Doe",
"userPrincipalName": "johndoe_gmail.com#EXT##johndoegmail.onmicrosoft.com",
}
As we can see, the mail property of this response is null. I am however able to get the user email by decoding the jwt id_token passed along with the access_token when calling the /token endpoint.
By applying the decodeJwtToken() function (attached at the end of this post) to the id_token, I am able to get the user email from the result
Object {
"aio": "xxxxxxxx",
"amr": Array [
"pwd",
],
"aud": "xxxxx",
"email": "johndoe#gmail.com",
"exp": xxxxxx,
"family_name": "Doe",
"given_name": "John",
"iat": xxxxxx,
"idp": "live.com",
"ipaddr": "xxx.xxx.xxx.xxx",
"iss": "https://sts.windows.net/xxxx/",
"name": "John Doe",
"nbf": xxxx,
"nonce": "xxx",
"oid": "xxxxxxx",
"sub": "xxxxx",
"tid": "xxxxx",
"unique_name": "live.com#johndoe#gmail.com",
"uti": "xxxx",
"ver": "1.0",
}
The decoding function looks as follows:
decodeIdToken = (token) => {
var base64Url = token.split('.')[1];
var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
var jsonPayload = decodeURIComponent(Buffer.from(base64, 'base64').toString().split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
return JSON.parse(jsonPayload);
};
For me, https://graph.microsoft.com/v1.0/users/<userid>/authentication/emailMethods endpoint worked. For this, the client must have UserAuthenticationMethod.Read.All permission. One can find more documentation here.
Sample reponse:
{
"#odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('8225f1cd-8025-4f8b-bf94-c595e02e3403')/authentication/emailMethods",
"value": [
{
"id": "3ddfcfc8-9383-446f-83cc-3ab9be4be18f",
"emailAddress": "kim#contoso.com"
}
]
}
var user = graphServiceClient.Users[UserId].Request().Select(x=>x.Identities).GetAsync();
var email = user.Identities.FirstOrDefault().IssuerAssignedId;
create a trial account in microsoft office 365 business premium using the below link:
https://signup.microsoft.com/Signup?OfferId=467eab54-127b-42d3-b046-3844b860bebf&dl=O365_BUSINESS_PREMIUM&culture=en-IN&country=IN&ali=1
Follow the steps while creating the account. It will allow us to create users in office 365. These users are like internal users of an organization. Now open azure portal with the above credential. All users of office 365 will be imported in active azure directory.
Now register an application with Read users basic profile delegated permission in active azure directory. note down client id , client secret and tenant domain to get access token for service to service authentication. This access token can be used to get user records which will be containing mail field as abc#.onmicrosoft.com

Resources