We are setting up Microsoft Azure Active Directory as an SSO solution for our mobile app but want to manage the account creation for users via the server side Microsoft Graph API.
For internal users of the domain, this works perfectly as we are using the Graph API as an admin user to create the accounts.
But, when trying to create an external account, (say joe.bloggs#gmail.com), this fails.
We are using the API call:
POST https://graph.microsoft.com/v1.0/users
BODY:
{
"accountEnabled": true,
"mailNickname": "joe.bloggs",
"displayName": "Joe Bloggs",
"givenName": "Joe",
"surname": "Bloggs",
"userPrincipalName": "joe.bloggs#gmail.com",
"passwordProfile" : {
"forceChangePasswordNextSignIn": false,
"password": "somepassword"
}
}
RESPONSE:
{
"error": {
"code": "Request_BadRequest",
"message": "Property userPrincipalName is invalid.",
"innerError": {
"request-id": "619450ec-e703-4a12-86e3-8f53c20d55fc",
"date": "2018-01-17T16:30:37"
},
"details": [
{
"target": "userPrincipalName",
"code": "InvalidValue"
}
]
}
}
It is saying the "userPrincipalName" is invalid, but after reviewing the documentation I'm not sure if the API supports external accounts or not?
NOTE: I realise you can use the "/beta/invitations" call but this does not create accounts.
I assume that you'r using Azure AD B2B and want to add new guest users to your Directory.
One thing I want to make clear is that you can invite guest users to your Directory , but you cannot create guest users directly in your Directory.
So, you can invite guest users with this Microsoft Graph API:
Request
POST https://graph.microsoft.com/beta/invitations
Content-type: application/json
Content-length: 551
{
"invitedUserEmailAddress": "yyy#test.com",
"inviteRedirectUrl": "https://myapp.com"
}
Response
HTTP/1.1 201 OK
Content-type: application/json
Content-length: 551
{
"id": "7b92124c-9fa9-406f-8b8e-225df8376ba9",
"inviteRedeemUrl": "https://invitations.microsoft.com/redeem/?tenant=04dcc6ab-388a-4559-b527-fbec656300ea&user=7b92124c-9fa9-406f-8b8e-225df8376ba9&ticket=VV9dmiExBsfRIVNFjb9ITj9VXAd07Ypv4gTg%2f8PiuJs%3d&lc=1033&ver=2.0",
"invitedUserDisplayName": "yyy",
"invitedUserEmailAddress": "yyy#test.com",
"sendInvitationMessage": false,
"invitedUserMessageInfo": {
"messageLanguage": null,
"ccRecipients": [
{
"emailAddress": {
"name": null,
"address": null
}
}
],
"customizedMessageBody": null
},
"inviteRedirectUrl": "https://myapp.com/",
"status": "Completed",
"invitedUser": [ { "id": "243b1de4-ad9f-421c-a933-d55305fb165d" } ]
}
Additional, if you want to invite guest users without an invitation, please refer to this document.
Hope this helps!
Related
I have a with yeoman teams scaffolded tab in which I ask for adminconsent for application permission.
On a Go backend I then use the code to retrieve the access token. I am also getting all Users of the team.
Although I am using only application permissions, I can not send notifications to the admin account. When trying to send notifications to other accounts, the Graph API returns a 204 Status Code but the activity feed of the recipient stays empty.
The application has following application permissions (with granted admin consent):
ChannelMember.ReadAll
GroupMember.ReadAll
Team.ReadBasic.All
TeamMember.Read.All
TeamsActivity.Send
This is the Manifest file with version 1.11:
{
"manifestVersion": "1.11",
"id": "{{APPLICATION_ID}}",
"version": "{{VERSION}}",
"permissions": [
"identity",
"messageTeamMembers"
],
"webApplicationInfo": {
"id": "{{APPLICATION_ID}}",
"resource": "api://{{DOMAIN}}.{{APPLICATION_ID}}"
},
"devicePermissions": [
"notifications"
],
"activities": {
"activityTypes": [
{
"type": "newActivity",
"description": "A new Activity Description"
}
]
}
}
The HTTP POST request to: https://graph.microsoft.com/v1.0/teams/{teamID}/sendNotification
The JSON Body:
{
"topic": {
"source": "entityUrl",
"value": "https://graph.microsoft.com/v1.0/teams/%s"
},
"activityType": "newActivity",
"previewText": {
"content": "A new Activity Description"
},
"recipient": {
"odata.type": "microsoft.graph.aadUserNotificationRecipient",
"userId": "{{USER_ID}}"
}
}
The recipient is also Member of the channel the activity is referring to. I also made sure that the recipient can receive notifications.
I recently deployed a Node.js/express application to a Google Cloud Compute VM. How do I send it get requests? I followed the instructions in this article to get my URL. When I curl it from the command line, though, I get a 401 authentication credential error.
Request:
https://compute.googleapis.com/compute/v1/projects/*PROJECT_ID*/zones/*ZONE*/machineTypes/e2-standard-2
I am already logged into the gcloud SDK--is there some kind of other auth I need to network with my VM?
Full Error Message:
{
"error": {
"code": 401,
"message": "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"errors": [
{
"message": "Login Required.",
"domain": "global",
"reason": "required",
"location": "Authorization",
"locationType": "header"
}
],
"status": "UNAUTHENTICATED",
"details": [
{
"#type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "CREDENTIALS_MISSING",
"domain": "googleapis.com",
"metadata": {
"method": "compute.v1.MachineTypesService.Get",
"service": "compute.googleapis.com"
}
}
]
}
}
I have a REST API on the internet that is secured with Azure AD and a required header (custom apikey).
You can call this API in example with postman using the client credentials flow. This all works fine.
I'm now developing a custom Azure DevOps extension that is showing the information returned from that API in the workitem form of ADO. I can call the API (if I remove the security from the API) from the extension through a generic Service Connection. Now I want to get the security working.
So I want to create a custom service connection that will get an accesstoken from the azure AD using ClientID and ClientSecret (client credentials flow).
When I have that working, I can call my API on a secure way.
Also how do I add the mandatory header to the call to the api? I need to add the header as field to the service connection as well right?
So I think I end up with a custom service connection instance that asks for TenantId, ClientId, Client Secret, Audience/Scope, List of headers (name/value).
For a more reference blog post I used this one: https://thingswithcode.blogspot.com/2019/07/using-azure-devops-service-connections.html
{
"id": "api-service-connection",
"description": "Service connection for api",
"type": "ms.vss-endpoint.service-endpoint-type",
"targets": [
"ms.vss-endpoint.endpoint-types"
],
"properties": {
"name": "Call API",
"displayName": "Call API",
"icon": "img/world.png",
"url": {
"displayName": "API Url",
"value": "https://path-to-api",
"helpText": "Url of the API to connect to."
},
"inputDescriptors": [
{
"id": "api-key",
"name": "API Key",
"description": "The value for the header 'ApiKey'",
"inputMode": "textbox",
"isConfidential": false,
"validation": {
"isRequired": true,
"dataType": "string"
}
}
],
"authenticationSchemes": [
{
"type": "ms.vss-endpoint.endpoint-auth-scheme-oauth2" // this is wrong...
}
],
"headers": [
{
"name": "ApiKey",
"value": "{{endpoint.api-key}}"
}
],
"helpMarkDown": "<b>Learn more</b>"
}
}
Thanks
We have a .net core application which uses Azure AD for authentication (MSAL/ v2.0). We want a linux application to access an API from the first application. The second application has no user context and will interact exactly as curl script would.
From reading the documentation I believe that I should register a second application with azure ad. I can get so far as a JWT token that has the target application as audience but I cannot get access to the api. We've been able to access the (real) api from scripts with an access token captured from a logged in user.
I created a Test environment to find the solution.
Created a .net core project authenticated by azure ad. It is running locally from my workstation not deployed to azure. Azure Authentication is working for interactive users.
Registered a second application and created a secret for it.
The first application is configured with both id and access tokens for implicit grant, it is not set as a public client.
I defined an approle 'access_as_application' in the manifest.
Under Expose an API I Created a scope 'api' and added the other application as an authorized client application.
Under API permissions I added a permisison, chose application permission and checked the approle I created earlier.
I can run a curl script and retrieve a bearer token that when decoded shows an audience matching my application. When I use that token in a curl script it is redirected to sign in.
Manifest:
{
"id": "33b*******************************",
"acceptMappedClaims": null,
"accessTokenAcceptedVersion": 2,
"addIns": [],
"allowPublicClient": null,
"appId": "3d8*******************************",
"appRoles": [
{
"allowedMemberTypes": [
"Application"
],
"description": "Access webapp as an application.",
"displayName": "access_as_application",
"id": "ff5ea9b2*******************************",",
"isEnabled": true,
"lang": null,
"origin": "Application",
"value": "access_as_application"
}
],
"oauth2AllowUrlPathMatching": false,
"createdDateTime": "2019-10-29T16:49:37Z",
"groupMembershipClaims": null,
"identifierUris": [
"api://3d8*******************************"
],
"informationalUrls": {
"termsOfService": null,
"support": null,
"privacy": null,
"marketing": null
},
"keyCredentials": [],
"knownClientApplications": [],
"logoUrl": null,
"logoutUrl": "https://localhost:44321/signout-callback-oidc",
"name": "WebApp",
"oauth2AllowIdTokenImplicitFlow": true,
"oauth2AllowImplicitFlow": true,
"oauth2Permissions": [
{
"adminConsentDescription": "consent for api",
"adminConsentDisplayName": "consent for api",
"id": "a4b2*******************************",",
"isEnabled": true,
"lang": null,
"origin": "Application",
"type": "Admin",
"userConsentDescription": null,
"userConsentDisplayName": null,
"value": "api"
}
],
"oauth2RequirePostResponse": false,
"optionalClaims": null,
"orgRestrictions": [],
"parentalControlSettings": {
"countriesBlockedForMinors": [],
"legalAgeGroupRule": "Allow"
},
"passwordCredentials": [
{
"customKeyIdentifier": null,
"endDate": "2299-12-31T05:00:00Z",
"keyId": "e03c4*******************************",",
"startDate": "2019-10-31T20:05:42.56Z",
"value": null,
"createdOn": "2019-10-31T20:05:42.7555795Z",
"hint": "00_",
"displayName": "webappsecret"
}
],
"preAuthorizedApplications": [
{
"appId": "a4b*******************************",
"permissionIds": [
"23b*******************************"
]
},
{
"appId": "3d8*******************************",
"permissionIds": [
"23b*******************************"
]
}
],
"publisherDomain": "brainbuzgmail.onmicrosoft.com",
"replyUrlsWithType": [
{
"url": "https://localhost:44321/signin-oidc",
"type": "Web"
},
{
"url": "https://localhost:44321/",
"type": "Web"
}
],
"requiredResourceAccess": [
{
"resourceAppId": "3d8*******************************",
"resourceAccess": [
{
"id": "23b*******************************",
"type": "Scope"
},
{
"id": "ff5ea*******************************",",
"type": "Role"
}
]
},
{
"resourceAppId": "a4b*******************************",
"resourceAccess": [
{
"id": "a37a*******************************",",
"type": "Scope"
},
{
"id": "ccf78*******************************",",
"type": "Role"
}
]
},
{
"resourceAppId": "00000003-0000-0000-c000-000000000000",
"resourceAccess": [
{
"id": "e1fe*******************************",",
"type": "Scope"
}
]
}
],
"samlMetadataUrl": null,
"signInUrl": null,
"signInAudience": "AzureADMyOrg",
"tags": [],
"tokenEncryptionKeyId": null
}
Variables $ are set in environment. Token is captured from successful request and set as $TOKEN. -k insecure flag is used in curl due to local app using self signed certificate.
curl -X POST -d "grant_type=client_credentials&client_id=$CLIENTID&client_secret=$SECRET&resource=$SCOPE" https://login.microsoftonline.com/$TENANT/oauth2/token
curl -k 'https://localhost:44321/api/' \
-H 'Accept: application/json' \
-H "Authorization: Bearer $TOKEN" \
-H 'Sec-Fetch-Mode: cors' -H 'Content-Type: application/json' --compressed
Partial response redirecting to login page, it looks like the client is being asked to get an id token, even though it has a valid access token:
< HTTP/2 302
< location: https://login.microsoftonline.com/****/oauth2/v2.0/authorize?client_id=***&redirect_uri=https%3A%2F%2Flocalhost%3A44321%2Fsignin-oidc&response_type=id_token
If you just want your Linux app to call APIs of your .net core application which protected by Azure AD,this is a service to service call flow and there is no need to redirect to /authorize endpoint as generally this endpoint is one of the steps of users login.
Based on your description, you have obtained access token successfully , and you can use this token as a Authorization Bearer header in your API request to call your .net core application directly.
TodoListServicepeoject of this demo is a sample API side demo which will be helpful for you. In your case you should do some modify to make service to service call work .
1.Replace content of Controllers/TodoListController.cs in TodoListService with code below :
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Web.Resource;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using TodoListService.Models;
namespace TodoListService.Controllers
{
[Authorize]
[Route("api/[controller]")]
public class TodoListController : Controller
{
static readonly ConcurrentBag<TodoItem> TodoStore = new ConcurrentBag<TodoItem>();
/// <summary>
/// The Web API will only accept tokens 1) for users, and
/// 2) having the access_as_user scope for this API
/// </summary>
static readonly string[] scopeRequiredByApi = new string[] { "access_as_application" };
// GET: api/values
[HttpGet]
public IEnumerable<TodoItem> Get()
{
// HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
string owner = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
return TodoStore.Where(t => t.Owner == owner).ToList();
}
// POST api/values
[HttpPost]
public void Post([FromBody]TodoItem todo)
{
//check roles claim in token start
Claim scopeClaim = HttpContext.User?.FindFirst("http://schemas.microsoft.com/ws/2008/06/identity/claims/role");
if (scopeClaim == null || !scopeClaim.Value.Split(' ').Intersect(scopeRequiredByApi).Any())
{
HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
string message = $"The 'roles' claim does not contain scopes '{string.Join(",", scopeRequiredByApi)}' or was not found";
throw new HttpRequestException(message);
}
//check roles claim end
string owner = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
TodoStore.Add(new TodoItem { Owner = owner, Title = "test!!" });
}
}
}
In WebApiServiceCollectionExtensions.cs of Microsoft.Identity.Web project,line 80 , replace with code below to make sure your role claim will be checked :
&& !context.Principal.Claims.Any(y => y.Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"))
Have a test here , post a request :
get records :
Hope it helps .
I have an app that integrates with Office365 and I am attempting to create a calendar event on an Outlook calendar using the Microsoft Graph API. Here is what I have so far:
request.post({
url:'https://graph.microsoft.com/v1.0/me/events',
form: {
"Id": null,
"Subject": "Discuss the Calendar REST API",
"Body": {
"ContentType": "Text",
"Content": "This is some content."
},
"Start": {
"DateTime": "2016-01-24T18:00:00",
"TimeZone": "Pacific Standard Time"
},
"End": {
"DateTime": "2016-01-25T19:00:00",
"TimeZone": "Pacific Standard Time"
},
"ShowAs": "Free",
"IsReminderOn":false
},
headers: {
"Authorization": "Bearer " + access_token,
"Content-Type": "application/json"
}
}, function(err, httpResponse, body) {
if (err) {
console.log('addMicrosoftAccessToken() ERROR = ' + err);
callback(err, false);
} else {
console.log('httpResponse = ' + JSON.stringify(httpResponse));
callback(null, true);
}
})
The problem is that the event isn't saved on the users Outlook calendar. Also, I'm not getting an error in the log. I suspect that I'm not sending the proper form data in the request. Any ideas?
UPDATE: Here is the httpResponse I'm getting in the log:
{
"statusCode": 500,
"body": "{\r\n \"error\": {\r\n \"code\": \"UnknownError\",\r\n \"message\": \"\",\r\n \"innerError\": {\r\n \"request-id\": \"8ebe2efc-649c-4d8d-bee1-be2457cc3a45\",\r\n \"date\": \"2016-01-25T19:05:27\"\r\n }\r\n }\r\n}",
"headers": {
"cache-control": "private",
"transfer-encoding": "chunked",
"content-type": "application/json",
"server": "Microsoft-IIS/8.5",
"request-id": "8ebe2efc-649c-4d8d-bee1-be2457cc3a45",
"client-request-id": "8ebe2efc-649c-4d8d-bee1-be2457cc3a45",
"x-ms-ags-diagnostic": "{\"ServerInfo\":{\"DataCenter\":\"East US\",\"Slice\":\"SliceB\",\"ScaleUnit\":\"000\",\"Host\":\"AGSFE_IN_4\",\"ADSiteName\":\"EST\"}}",
"outboundduration": "707.5019",
"duration": "713.2419",
"x-powered-by": "ASP.NET",
"date": "Mon, 25 Jan 2016 19:05:27 GMT",
"connection": "close"
},
"request": {
"uri": {
"protocol": "https:",
"slashes": true,
"auth": null,
"host": "graph.microsoft.com",
"port": 443,
"hostname": "graph.microsoft.com",
"hash": null,
"search": null,
"query": null,
"pathname": "/v1.0/me/events",
"path": "/v1.0/me/events",
"href": "https://graph.microsoft.com/v1.0/me/events"
},
"method": "POST",
"headers": {
"Authorization": "Bearer blah blah",
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json",
"content-length": 643
}
}
}
UPDATE 2:
This link is titled "Create Event", and appears to have the response listed in both the request and response sections, making it particularly confusing:
http://graph.microsoft.io/docs/api-reference/v1.0/api/event_post_instances
Also, in the above link where it lists
POST https://graph.microsoft.com/v1.0/me/events/<id>/instances
what is <id>? It doesn't tell me what id is supposed to be?
UPDATE 3: This link is also titled "Create Event", yet it has a different POST URL:
http://graph.microsoft.io/docs/api-reference/v1.0/api/user_post_events
Very confusing.
The reason why your me/events request fails is that you used the Azure Active Directory (AAD) authorization flow for accessing your personal Microsoft Account (Live Id) calendar. AAD allows creating users mapped to Microsoft Accounts such that when requesting an AAD token you're signing in with your Microsoft Account credentials. That way your credentials can be used to access both business (work or school) and consumer (personal) services. While the credentials are shared there are 2 distinct user accounts. When accessing business services like OneDrive for Business or SharePoint you need to sign in with the AAD user account in the context of your work or school organization. When accessing consumer services like Hotmail or OneDrive you need to sing in with the Microsoft Account (Live Id). You request is attempting to access an Outlook account in the context of your organization, which doesn't exist as your email address is already served by a personal Outlook account associated with your Microsoft Account. Please use the converged authorization flow (http://graph.microsoft.io/en-us/docs/authorization/converged_auth) to sign in with your personal Microsoft Account and you'll be able to access your personal email and calendar. Alternatively you can keep using the AAD authorization flow to access work or school calendars for AAD users not mapped to Microsoft Accounts.