Azure AD: Create user with extra identities - azure-active-directory

I'm having trouble creating a user in an Azure Active Directory with a custom identity. The body of my request looks like this:
{
"passwordProfile": {
"password": "password-value"
},
"accountEnabled": true,
"displayName": "FIRSTNAME LASTNAME",
"passwordPolicies": "DisablePasswordExpiration",
"creationType": "LocalAccount",
"identities": [
{
"issuerAssignedId": "avalid#email.com",
"signInType": "emailAddress",
"issuer": "my_tenant.onmicrosoft.com"
}
]
}
I've also tried a version of the request where I specify mailNickname and userPrincipalName. In every case, the creation fails with the error:
{
"error": {
"innerError": {
"date": "2020-02-20T17:23:48",
"request-id": "c5a7c8da-35bd-4ae2-9ae8-6714b672f035"
},
"message": "One or more properties contains invalid values.",
"code": "Request_BadRequest"
}
}
There's a code snippet in the C# docs that suggests this should be possible.
What am I missing?

Microsoft Graph allows you to manage user accounts in your Azure AD B2C directory by providing create, read, update, and delete methods in the Microsoft Graph API. You can migrate an existing user store to an Azure AD B2C tenant and perform other user account management operations by calling the Microsoft Graph API.
If you try to use this Azure AD Graph API request for a normal Azure AD tenant, it will get the same error massage as yours.
So, ensure the tenant you're trying to query is a B2C tenant.
Try to use the global admin of the B2C tenant (e.g. username#b2ctenant.onmicrosoft.com) to obtain a token. Then use the token in the head to use the API :
Request:
POST https://graph.windows.net/myorganization/users?api-version=1.6
Body Content-Type: application/json:
{
"passwordProfile": {
"password": "password-value"
},
"accountEnabled": true,
"displayName": "FIRSTNAME LASTNAME",
"mailNickname": "mspcai",
"passwordPolicies": "DisablePasswordExpiration",
"creationType": "LocalAccount",
"identities": [
{
"issuerAssignedId": "avalid#email.com",
"signInType": "emailAddress",
"issuer": "my_tenant.onmicrosoft.com"
}
]
}

Looks like you are referring to a beta snippet, please try the following endpoint:
https://graph.microsoft.com/beta/users

Related

Which token from Google's Social Login API should you use for securing back-end data in a public-facing ReactJS app?

I am using the Google Sign-In API documented here: https://developers.google.com/identity/sign-in/web/sign-in to let users sign-in to a ReactJS app.
When control is back to the app, I see this json coming from the api:
{
"Ba": "999888988999898999999",
"wc": {
"token_type": "Bearer",
"access_token": "ya99.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_Gt9hSnCbfgLfXXJpV9oGnJypUrN5cwrS8YBBHctEMLumSPhLDMmGEauaiHoTAWsdDTEY9OEaaAK9AeQa58pUp9SjiElHGMiU8UXT8cOpXyUMIXhXydHaMkXj",
"scope": "email profile openid https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile",
"login_hint": "YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYpasAM9f89UaNK9wHcHDJrcH-KAPpI99eCwn8CdOE5YFBtCJQDCqaaI9pLyaaRqA",
"expires_in": 9599,
"id_token": "eyJhbGciOiJSUaI9NiIsImtpaCI8IjNkaDajYTJhODFkYaJmaWE9YaM9NDI9MaFlN9UyOTakMmQ9NWI9NDYiLCJ9eXAiOiJKV9QifQ.eyJpc9MiOiJhY9NvdW59cy5nb99nbGUuY99tIiwiYXpwIjoiMjk9NTg9MTA9NTQ9LXRlNWR9bnY9c9BhcHAaNac9dGpraWptNWp9cjBmNjBnLmFwcHMua99va9xldXNlcmNvbnRlbnQuY99tIiwiYXVkIjoiMjk9NTg9MTA9NTQ9LXRlNWR9bnY9c9BhcHAaNac9dGpraWptNWp9cjBmNjBnLmFwcHMua99va9xldXNlcmNvbnRlbnQuY99tIiwic9ViIjoiMTEaNag9Mjg9MDIxNjQ9MjIyMTI5IiwiaW9haWwiOiJ9aXN9dXNlcjMxN9hAa99haWwuY99tIiwiaW9haWxfdmVyaWapaWQiOnRydWUsImF9X9hhc9giOiJuV9h5b9tTRFFXMFFMVkJOWlaNTja9IiwibmFtaSI8IlRlc9QgVXNlciIsInBpY9R9cmUiOiJodHRwcaovL9xoMy5nb99nbGV9c9VyY99udGVudC5jb99vYS9BQVRYQUp9dlo5alNuRGtKakkwS99iM9FmdTJfR9haMXFjLVhkSVltc9tyOD9aOTYtYyIsImdpdmVuX95hbWUiOiJUaXN9IiwiamFtaWx5X95hbWUiOiJVc9VyIiwibG9jYWxlIjoiaW9iLCJpYXQiOjE9NDY9MTA9MaUsImV9cCI8MTY9NjUxNDIaNSwianRpIjoiYTkwN9JjNTljNDUyNjljaDJmYTE5OGYyNGYwY9E9NjIyM9VjaTgaYiJ9.HYIamlf98xyo9ah9WennYB8ob8N9JaOxs8drmA8PdOy89emO8R_UVn9_F9YmKsBicMYMmUeis-9_hBijayOahDXQuEhu8gm8AsAhdq5GfP89hfb9G9h9UyPEdMWb9y9w-q9EayXsArDp9A9yT8AGGaRpF5i-tNmtwL8mlDJaFyh9UuIMlyCpYjhyD9lbCvGcVI9dbogcMkal-PnxY9S58Uw8SFsyOLJqWk9bTmQNqjQrwbBooWav99IxcDXjnJixmp9naTxBqAv5J9mEpFwQKbKSbbcNraJQLJvuacVEAVCR9ueOVOXlbOehj8b8SRlB89rrjgjmrqb9LdsMit8kew",
"session_state": {
"extraQueryParams": {
"authuser": "9"
}
},
"first_issued_at": 9898599895889,
"expires_at": 9898599999889,
"idpId": "google"
},
"Du": {
"FW": "999888988999898999999",
"tf": "Test User",
"VX": "Test",
"iW": "User",
"eN": "https://lh9.googleusercontent.com/a/AATXAJxva9fSnDkJjI9K_b9Afu9_Gha9qc-XdIYmsKr8=s98-c",
"tv": "xxxxxxxxxxxx#gmail.com"
},
"googleId": "999888988999898999999",
"tokenObj": {
"token_type": "Bearer",
"access_token": "ya99.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_Gt9hSnCbfgLfXXJpV9oGnJypUrN5cwrS8YBBHctEMLumSPhLDMmGEauaiHoTAWsdDTEY9OEaaAK9AeQa58pUp9SjiElHGMiU8UXT8cOpXyUMIXhXydHaMkXj",
"scope": "email profile openid https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile",
"login_hint": "YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYpasAM9f89UaNK9wHcHDJrcH-KAPpI99eCwn8CdOE5YFBtCJQDCqaaI9pLyaaRqA",
"expires_in": 9599,
"id_token": "eyJhbGciOiJSUaI9NiIsImtpaCI8IjNkaDajYTJhODFkYaJmaWE9YaM9NDI9MaFlN9UyOTakMmQ9NWI9NDYiLCJ9eXAiOiJKV9QifQ.eyJpc9MiOiJhY9NvdW59cy5nb99nbGUuY99tIiwiYXpwIjoiMjk9NTg9MTA9NTQ9LXRlNWR9bnY9c9BhcHAaNac9dGpraWptNWp9cjBmNjBnLmFwcHMua99va9xldXNlcmNvbnRlbnQuY99tIiwiYXVkIjoiMjk9NTg9MTA9NTQ9LXRlNWR9bnY9c9BhcHAaNac9dGpraWptNWp9cjBmNjBnLmFwcHMua99va9xldXNlcmNvbnRlbnQuY99tIiwic9ViIjoiMTEaNag9Mjg9MDIxNjQ9MjIyMTI5IiwiaW9haWwiOiJ9aXN9dXNlcjMxN9hAa99haWwuY99tIiwiaW9haWxfdmVyaWapaWQiOnRydWUsImF9X9hhc9giOiJuV9h5b9tTRFFXMFFMVkJOWlaNTja9IiwibmFtaSI8IlRlc9QgVXNlciIsInBpY9R9cmUiOiJodHRwcaovL9xoMy5nb99nbGV9c9VyY99udGVudC5jb99vYS9BQVRYQUp9dlo5alNuRGtKakkwS99iM9FmdTJfR9haMXFjLVhkSVltc9tyOD9aOTYtYyIsImdpdmVuX95hbWUiOiJUaXN9IiwiamFtaWx5X95hbWUiOiJVc9VyIiwibG9jYWxlIjoiaW9iLCJpYXQiOjE9NDY9MTA9MaUsImV9cCI8MTY9NjUxNDIaNSwianRpIjoiYTkwN9JjNTljNDUyNjljaDJmYTE5OGYyNGYwY9E9NjIyM9VjaTgaYiJ9.HYIamlf98xyo9ah9WennYB8ob8N9JaOxs8drmA8PdOy89emO8R_UVn9_F9YmKsBicMYMmUeis-9_hBijayOahDXQuEhu8gm8AsAhdq5GfP89hfb9G9h9UyPEdMWb9y9w-q9EayXsArDp9A9yT8AGGaRpF5i-tNmtwL8mlDJaFyh9UuIMlyCpYjhyD9lbCvGcVI9dbogcMkal-PnxY9S58Uw8SFsyOLJqWk9bTmQNqjQrwbBooWav99IxcDXjnJixmp9naTxBqAv5J9mEpFwQKbKSbbcNraJQLJvuacVEAVCR9ueOVOXlbOehj8b8SRlB89rrjgjmrqb9LdsMit8kew",
"session_state": {
"extraQueryParams": {
"authuser": "9"
}
},
"first_issued_at": 9898599895889,
"expires_at": 9898599999889,
"idpId": "google"
},
"tokenId": "eyJhbGciOiJSUaI9NiIsImtpaCI8IjNkaDajYTJhODFkYaJmaWE9YaM9NDI9MaFlN9UyOTakMmQ9NWI9NDYiLCJ9eXAiOiJKV9QifQ.eyJpc9MiOiJhY9NvdW59cy5nb99nbGUuY99tIiwiYXpwIjoiMjk9NTg9MTA9NTQ9LXRlNWR9bnY9c9BhcHAaNac9dGpraWptNWp9cjBmNjBnLmFwcHMua99va9xldXNlcmNvbnRlbnQbnQuY99tIiwic9ViIjoiMTEaNag9Mjg9MDIxNjQ9MjIyMTI5IiwiaW9haWwiOiJ9aXN9dXNlcjMxN9hAa99haWwuY99tIiwiaW9haWxfdmVyaWapaWQiOnRydWUsImF9X9hhc9giOiJuV9h5b9tTRFFXMFFMVkJOWlaNTja9IiwibmFtaSI8IlRlc9QgVXNlciIsInBpY9R9cmUiOiJodHRwcaovL9xoMy5nb99nbGV9c9VyY99udGVudC5jb99vYS9BQVRYQUp9dlo5alNuRGtKakkwS99iM9FmdTJfR9haMXFjLVhkSVltc9tyOD9aOTYtYyIsImdpdmVuX95hbWUiOiJUaXN9IiwiamFtaWx5X95hbWUiOiJVc9VyIiwibG9jYWxlIjoiaW9iLCJpYXQiOjE9NDY9MTA9MaUsImV9cCI8MTY9NjUxNDIaNSwianRpIjoiYTkwN9JjNTljNDUyNjljaDJmYTE5OGYyNGYwY9E9NjIyM9VjaTgaYiJ9.HYIamlf98xyo9ah9WennYB8ob8N9JaOxs8drmA8PdOy89emO8R_UVn9_F9YmKsBicMYMmUeis-9_hBijayOahDXQuEhu8gm8AsAhdq5GfP89hfb9G9h9UyPEdMWb9y9w-q9EayXsArDp9A9yT8AGGaRpF5i-tNmtwL8mlDJaFyh9UuIMlyCpYjhyD9lbCvGcVI9dbogcMkal-PnxY9S58Uw8SFsyOLJqWk9bTmQNqjQrwbBooWav99IxcDXjnJixmp9naTxBqAv5J9mEpFwQKbKSbbcNraJQLJvuacVEAVCR9ueOVOXlbOehj8b8SRlB89rrjgjmrqb9LdsMit8kew",
"accessToken": "ya99.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_Gt9hSnCbfgLfXXJpV9oGnJypUrN5cwrS8YBBHctEMLumSPhLDMmGEauaiHoTAWsdDTEY9OEaaAK9AeQa58pUp9SjiElHGMiU8UXT8cOpXyUMIXhXydHaMkXj",
"profileObj": {
"googleId": "999888988999898999999",
"imageUrl": "https://lh9.googleusercontent.com/a/AAXXXXXXXI9K_b9Afu9_Gha9qc-XdIYmsKr8=s98-c",
"email": "xxxxxxxxxxxx#gmail.com",
"name": "Test User",
"givenName": "Test",
"familyName": "User"
}
}
Now I want to store some data associated with the logged-in user, for example, their site preferences, and associate it to their social login.
If this was a regular non-social user with just an ID and Password, I would use a secret derived from the ID & password to authenticate the user to a custom back-end API when storing and retrieving the data. This way an attacker hacking the ReactJS app cannot retrieve information associated with a different user.
However with this social login, there is no password. What should I use instead from the Google Response to secure the call to the back-end?
I presume I need a token which is generated by Google, and is permanent as well as unique to both the user and my web app. Unless I'm doing something completely wrong and what I need is for the back-end API to talk to Google directly. How is this supposed to work?
WHAT HAVE I TRIED SO FAR
I have placed a console.log() call after the Google API call, which let me inspect the json returned as per the above. Then I realized the json is not self-explanatory at all, and this is not something I can solve by trial-and-error. Also, I checked the documentation and discovered that it says nothing about this scenario.

AWS unable to assume role in React via JS SDK, access denied

I have 2 accounts, one I will call RootAccount where I created an organization and a child account called ChildAccount.
I created a user in the RootAccount called RootUser and a policy in the RootAccount to grant the assume right to the RootUser. I want to create more accounts automatically in the future that's why I limit it to all roles within my children. This is the policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:PrincipalOrgID": "o-xxxxxxxxxx"
}
}
}
]
}
then I created a role in the ChildAccount called ChildRole with this trust policy:
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::1111111111:user/RootUser"
},
"Action": "sts:AssumeRole"
}
]
}
where 1111111111 is the account number of the RootAccount.
Then I created a React app with some sample code where I want assume the role from my RootUser:
const AWS = require("aws-sdk");
AWS.config.update({ accessKeyId: 'RootUserAccessKey', secretAccessKey: 'RootUserSecret', region: "eu-central-1" });
const sts = new AWS.STS()
const checkRights = async () => {
const params = {
RoleArn: 'Arn of ChildRole',
RoleSessionName: 'testsession',
DurationSeconds: 900
}
const session = sts.assumeRole(params,function(err:any,data:any){
if(err)
{
console.log('err:',err)
}
console.log('data:',data)
}).promise()
}
and whenever I run checkRights() I receive an error message 403 Access denied:
AccessDenied: User: {Arn of RootUser} is not authorized to perform: sts:AssumeRole on resource: {Arn of ChildRole}
Any idea what I might be missing? Found an Error in my code/policies? I doublechecked the OrgID, ARNs and assigned the proper policies but it didn't help.
Thanks in advance!
Regards Christian
EDIT:
I removed the PrincipalOrgID Condition but it didn't help, I still receive the same error message. I removed the role, assigned the Assume All Rule and waited ~25 minutes. I also restarted the node server.
What else could be wrong? Or does it take longer until the permissions are updated?
The AWS docs say that this condition key is for resource-based policies. However, you are trying to use in Identity-based policies:
aws:PrincipalOrgID – Simplifies specifying the Principal element in a resource-based policy.
This could explain why your policies don't work the way you expect them to work.
After a lot of troubleshooting and acquiring the aws dev support plan I renewed my credentials for the user and this solved it

JS Report Server Console error: Authorization server has no "username" field in its response, token assumed as invalid

My js report server with client ui applications was working with username filed by adding one claim e.g. Claim('username', 'user name value') in identityserver.
4 version 1.1. B ut recently client has upgraded the identityserver 4 version to 2.2.0 and netcoreapp2.1 .
Now the client ui applications get "Unauthorized" error. When I run the application locally I see one error in jsreport server console:
error: Authorization server has no "username" field in its response, token assumed as invalid.
I have tried to find solution in the sample:
https://github.com/bjrmatos/jsreport-with-authorization-server-sample but they have not upgraded the sample for latest .net core and identity server yet, I see the node js sample link "https://github.com/IdentityServer/IdentityServer4.Samples/tree/release/NodeJsApi" does not exists. so I am unable to solve the issue. Can anyone help me please on this?
Thanks in advance.
I have found solution to solve this error by adding a claim type with user name and declare that in api resource scope:
new ApiResource
{
Name="jsreportscope",
DisplayName = "JavaScript based reporting platform",
Scopes=
{
new Scope()
{
Name="jsreportscope",
UserClaims = new List<string>(){"username" }
}
},
UserClaims = new List<string>(){"username"},
ApiSecrets = { new Secret("yoursecret".Sha256()) },
}
But now it goes to the previous problem that I fixed by adding a claim type matching the value of username field value with identity server 1.1 version but now we have upgraded the identity server version to 2.1 and again getting the error. The it was able to authorize any user of identity server for report access. Here is the jsreport.config.js code I am using:
{
"store": { "provider": "fs" },
"httpPort": 5004,
"allowLocalFilesAccess": true,
"extensions": {
"authentication": {
"cookieSession": {
"secret": "<your strong secret>"
},
"admin": {
"username": "IdentityServer4User#domain.com",
"password": "Password"
},
"authorizationServer": {
"tokenValidation": {
"endpoint": "http://localhost:5000/connect/introspect",
"usernameField": "username",
"activeField": "active",
"scope": {
"valid": ["jsreportscope"]
},
"auth": {
"type": "basic",
"basic": {
"clientId": "jsreport",
"clientSecret": "yoursecret"
}
}
}
},
"enabled": true
},
"authorization": {
"enabled": true
},
"sample-template": {
"createSamples": true
}
}
}
But now I can login and access reports from report server if login by only the user IdentityServer4User#domain.com any other users is getting unauthorized error. In the report server console the error is shown like:
error: username "OtherIdentityServer4User#domain.com" returned from authorization server is not a jsreport user. I don not want to add all the identity server users to js report server.

Problems with Azure application manifest trying to authenticate with office-js-helpers in an Outlook web add-in

I'm using office-js-helpers in order to get an OAuth token in my Outlook web add-in so I can use it for OAuthCredentials with the EWS Managed API (code for that is in an Azure App Service using the ASP.NET Web API).
I have configured my app's application registration in my test Office 365 tenant (e.g. mytenant.onmicrosoft.com, which is NOT the same Azure subscription hosting the web app - if that matters) as a Native app with oauth2AllowImplicitFlow set to true. I used a Native app type instead of a Web/API app to bypass an unexpected error indicating my app requires admin consent - even though no application permissions were requested - but that's another story (perhaps I must use Native anyway - not 100% sure).
I made sure that the Redirect URI (aka reply URL) in the app registration points to the same page as the Outlook add-in (e.g. https://mywebapp.azurewebsites.net/MessageRead.html).
Here is my app manifest:
{
"appId": "a11aaa11-1a5c-484a-b1d6-86c298e8f250",
"appRoles": [],
"availableToOtherTenants": true,
"displayName": "My App",
"errorUrl": null,
"groupMembershipClaims": null,
"optionalClaims": null,
"acceptMappedClaims": null,
"homepage": "https://myapp.azurewebsites.net/MessageRead.html",
"identifierUris": [],
"keyCredentials": [],
"knownClientApplications": [],
"logoutUrl": null,
"oauth2AllowImplicitFlow": true,
"oauth2AllowUrlPathMatching": false,
"oauth2Permissions": [],
"oauth2RequiredPostResponse": false,
"objectId": "a11aaa11-99a1-4044-a950-937b484deb8e",
"passwordCredentials": [],
"publicClient": true,
"supportsConvergence": null,
"replyUrls": [
"https://myapp.azurewebsites.net/MessageRead.html"
],
"requiredResourceAccess": [
{
"resourceAppId": "00000003-0000-0000-c000-000000000000",
"resourceAccess": [
{
"id": "e1fe6dd8-ba31-4d61-89e7-88639da4683d",
"type": "Scope"
}
]
},
{
"resourceAppId": "00000002-0000-0000-c000-000000000000",
"resourceAccess": [
{
"id": "311a71cc-e848-46a1-bdf8-97ff7156d8e6",
"type": "Scope"
},
{
"id": "a42657d6-7f20-40e3-b6f0-cee03008a62a",
"type": "Scope"
}
]
},
{
"resourceAppId": "00000002-0000-0ff1-ce00-000000000000",
"resourceAccess": [
{
"id": "2e83d72d-8895-4b66-9eea-abb43449ab8b",
"type": "Scope"
},
{
"id": "ab4f2b77-0b06-4fc1-a9de-02113fc2ab7c",
"type": "Scope"
},
{
"id": "5eb43c10-865a-4259-960a-83946678f8dd",
"type": "Scope"
},
{
"id": "3b5f3d61-589b-4a3c-a359-5dd4b5ee5bd5",
"type": "Scope"
}
]
}
],
"samlMetadataUrl": null
}
I also made sure to add the authority URLs to my add-in's manifest:
<AppDomains>
<AppDomain>https://login.windows.net</AppDomain>
<AppDomain>https://login.microsoftonline.com</AppDomain>
</AppDomains>
This is the code I'm using in the add-in for the authentication with office-js-helpers:
// The Office initialize function must be run each time a new page is loaded.
Office.initialize = function(reason) {
$(document).ready(function () {
// Determine if we are running inside of an authentication dialog
// If so then just terminate the running function
if (OfficeHelpers.Authenticator.isAuthDialog()) {
// Adding code here isn't guaranteed to run as we need to close the dialog
// Currently we have no realistic way of determining when the dialog is completely
// closed.
return;
}
// Create a new instance of Authenticator
var authenticator = new OfficeHelpers.Authenticator();
authenticator.endpoints.registerAzureADAuth('a11aaa11-1a5c-484a-b1d6-86c298e8f250', 'mytenant.onmicrosoft.com');
// Add event handler to the button
$('#login').click(function () {
$('#token', window.parent.document).text('Authenticating...');
authenticator.authenticate('AzureAD', true)
.then(function (token) {
// Consume and store the acess token
$('#token', window.parent.document).text(prettify(token));
authToken = token.access_token;
})
.catch(function (error) {
// Handle the error
$('#token', window.parent.document).text(prettify(error));
});
});
});
};
Now the code in the add-in can properly sign in the user and ask for the required permissions, but after clicking the Accept button on the application authorization step the following error is returned:
AADSTS50011: The reply address 'https://mywebapp.azurewebsites.net' does not match the reply addresses configured for the application: 'a11aaa11-1a5c-484a-b1d6-86c298e8f250'. More details: not specified
The error now returns every time I click the Login button (the user is no longer prompted to sign in). It never did retrieve the token. The full auth URL is:
https://login.windows.net/mydomain.onmicrosoft.com/oauth2/authorize?response_type=token&client_id=a11aaa11-484a-b1d6-86c298e8f250&redirect_uri=https%3A%2F%2Fmywebapp.azurewebsites.net&resource=https%3A%2F%2Fgraph.microsoft.com&state=982599964&nonce=3994725115
What am I doing wrong? Could the issue actually be because the host name of the web app (the redirect URI) does not match the domain of the Azure AD tenant hosting the app registration? If so, how can I grant permissions to Exchange Online from my Azure subscription hosting the web app which does not have Office 365 or Exchange Online? Would I have to add an Azure subscription to my test Office 365 tenant so that it can also host a web application??
From your app manifest, I found that you used https://myapp.azurewebsites.net/MessageRead.html as one of the replyUrls.
And below is the url that you are using to get consent from user.
https://login.windows.net/mydomain.onmicrosoft.com/oauth2/authorize?response_type=token&client_id=a11aaa11-484a-b1d6-86c298e8f250&redirect_uri=https%3A%2F%2Fmywebapp.azurewebsites.net&resource=https%3A%2F%2Fgraph.microsoft.com&state=982599964&nonce=3994725115.
If you observe above url, you mentioned redirect_uri as https://myapp.azurewebsites.net. But redirect_uri should match with at least one of the replyUrls you mentioned in the app manifest.
Try to replace https://myapp.azurewebsites.net with https://myapp.azurewebsites.net/MessageRead.html in authorization url.
I have updated them in below url, if you want you can directly try below url.
https://login.windows.net/mydomain.onmicrosoft.com/oauth2/authorize?response_type=token&client_id=a11aaa11-484a-b1d6-86c298e8f250&redirect_uri=https%3A%2F%2Fmywebapp.azurewebsites.net%2FMessageRead.html&resource=https%3A%2F%2Fgraph.microsoft.com&state=982599964&nonce=3994725115

Angularjs Adal and additional claims or properties for Authorization

Scenario is Angularjs 1.6.5 app with a c# WebApi. Authentication is done against AAD with the use of angular-adal.js. Up to now, everything Works perfectly, as users are able to login through AAD and WebApi accepts the token.
For this specific app, the roles are in an External application, to which the WebApi has Access. I have been able to add the role claims (after fetching them from the External app) with the use of WindowsAzureActiveDirectoryBearerAuthenticationOptions with the following code inside the ConfigureOAuth(IAppBuilder app):
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
ValidAudience = clientId
},
//Audience = ConfigurationManager.AppSettings["ida:ClientID"],
Tenant = tenant,
Provider = new OAuthBearerAuthenticationProvider
{
OnValidateIdentity = async context =>
{
// Retrieve user JWT token from request.
var authorizationHeader = context.Request.Headers["Authorization"];
var userJwtToken = authorizationHeader.Substring("Bearer ".Length).Trim();
// Get current user identity from authentication ticket.
var authenticationTicket = context.Ticket;
var identity = authenticationTicket.Identity;
if (identity.FindFirst(System.Security.Claims.ClaimTypes.Role) == null)
{
var user = identity.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn").Value;
Cis.bll.Xrm.bllSystemUserRoles bllSystemUserRoles = new Cis.bll.Xrm.bllSystemUserRoles();
var su = bllSystemUserRoles.getByEmail(user);
//var roleClaim = new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Role, su.stringRoles);
foreach (var item in su.Roles)
{
identity.AddClaim(new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Role, item.xrmName));
}
}
}
}
});
So for each httpRequest that Angularjs does to the API, the previous function looks up the roles for the user and adds the role claims. With this implementation, I am able to use an AuthorizeAttribute in the Controller methods, restricting Access to only certain roles like so:
[CustomAuthorize(Constants.Roles.resourcesAdministrator)]
I find this way highly inneficient, because with each httpRequest, the API has to fetch the roles of the user from the database (or whatever persistance way is implemented).
What I want to do is to read the user roles just once, and then be able to use them in the API with every subsequent request. Is there a way to add the claims to the token AFTER we recieve the token for AAD?
BTW, I could just add a Roles property to each model, or something like that, but it is not what I'm looking for.
If you have any other ideas or suggestions, they will be greatly appreciated.
Regards
The token is not able to modified since it is issued. And since the roles is stored in the other application, I don't think it is possible to get the roles without query the database.
In this scenario, we can manage the roles though the Azure AD application roles & role claims. Then it will issue the roles claim in the id_token.
For example, we can modify the manifest of the app like below:
"appRoles": [
{
"allowedMemberTypes": [
"User"
],
"displayName": "Writer",
"id": "d1c2ade8-98f8-45fd-aa4a-6d06b947c66f",
"isEnabled": true,
"description": "Writers Have the ability to create tasks.",
"value": "Writer"
},
{
"allowedMemberTypes": [
"User"
],
"displayName": "Observer",
"id": "fcac0bdb-e45d-4cfc-9733-fbea156da358",
"isEnabled": true,
"description": "Observers only have the ability to view tasks and their statuses.",
"value": "Observer"
},
{
"allowedMemberTypes": [
"User"
],
"displayName": "Approver",
"id": "fc803414-3c61-4ebc-a5e5-cd1675c14bbb",
"isEnabled": true,
"description": "Approvers have the ability to change the status of tasks.",
"value": "Approver"
},
{
"allowedMemberTypes": [
"User"
],
"displayName": "Admin",
"id": "81e10148-16a8-432a-b86d-ef620c3e48ef",
"isEnabled": true,
"description": "Admins can manage roles and perform all task actions.",
"value": "Admin"
}
],
And assign the roles to the user through the application via Azure portal like figure below:
Then we can get the id_token like request below(implicit grant flow), the roles should be in the token. And we can call the web API using this token.
Get:https://login.microsoftonline.com/{tenant}/oauth2/authorize?response_type=id_token&client_id={clientId}&redirect_uri={redirect_uri}&nonce={nonce}
id_token sample:

Resources