Creating Microsoft Teams Session Using Graph Api
Intro
In this post we will attempt to create dynamically a Microsoft Teams meeting or session on behalf of a user.
Requirements
Before we can continue, you do need access to a Microsoft Entra environment as high enough credentials so we can create users and register application credentials. You also need access to a Windows computer for some powershell magic (no, despite powershell being available for Linux, it doesn't work). Finally you need a subscription with access to Teams (not all 365 packages offer this functionality mind you).
Phase 1: Registering An Application
- Go to the Entra id applications view.
- Click on "New registration".
- Fill in the application name and redirect URI (if needed).
- Click "Register".
- Once registered, note the Application (client) ID and Directory (tenant) ID.
- Under "Certificates & secrets", create a new client secret and note it down. This will be used for authentication.
- Under "API permissions", add the
OnlineMeetings.ReadWrite
permission. You may need to grant admin consent for this permission.
This application will be the one who is going to be authorized to make all those meetings. So at this point you should have three separate details noted down:
- Application (client) ID
- Directory (tenant) ID
- Application (client) secret value
Phase 2: Creating An Access Policy
Having app credentials alone will not do. The environment need to be told that it is okay for apps to do this kind of thing. That is done via an access policy which we will do on Windows with powershell.
1. Install required modules
Install-Module -Name MicrosoftTeams -Force -AllowClobber
2. Connecto teams
Connect-MicrosoftTeams
This command will pop up a login window where you need to enter you entra admin credentials.
If you get an error like this:
Connect-MicrosoftTeams : The 'Connect-MicrosoftTeams' command was found in the module 'MicrosoftTeams', but the
module could not be loaded. For more information, run 'Import-Module MicrosoftTeams'.
At line:1 char:1
+ Connect-MicrosoftTeams
You need to set execution policy to allow running scripts:
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
3. Creating The Policy
New-CsApplicationAccessPolicy -Identity "TeamsMeetingAccessPolicy" -AppIds "[CLIENT ID FROM APP REGISTRATION]" -Description "Allow this app to create meetings"
The policy is scoped to the app you registered as evident by the -AppIds argument so if you register a new app, the access policy would not apply out of the box.
4. Assigning The Access Policy To Users
So we have an app registered to manage Teams meeting and we have an access policy for it but there is one last step required: adding that access policy to individual users so the registered app can create the meetings on behalf of users. That too, is a powershell script.
Grant-CsApplicationAccessPolicy -PolicyName "TeamsMeetingAccessPolicy" -Identity [USER PRINCIPAL NAME]
With an organization of hundreds of thousands this can quickly get tedious so I have to assume there is a way to apply this policy environment wide or maybe by team basis instead of individuals. Granted, looping through all the users and adding this policy wouldn't be hard to script so even if there isn't, it's not mighty big deal.
Phase 3: Creating The Meeting
There are client software available for most programming languages to access Graph Api. I am using dotnet for this. We need three nugets to access Teams meetings:
- Azure.Identity
- Microsoft.Graph
- Microsoft.Identity.Client
If you aren't familiar with Graph Api, there is a wonderful Api Explorer (think, their version of Swagger) where you can try things out.
Getting An Access Token
We need to generate an access token in order to make calls to Graph Api. tenantId, clientId and clientSecret you got from Phase 1.
public static async Task<string> GetAccessTokenAsync(string tenantId, string clientId, string clientSecret)
{
Console.WriteLine("Getting access token for client '{0}' in tenant '{1}'.", clientId, tenantId);
using var client = new HttpClient();
var url = $"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token";
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("client_id", clientId),
new KeyValuePair<string, string>("scope", "https://graph.microsoft.com/.default"),
new KeyValuePair<string, string>("client_secret", clientSecret),
new KeyValuePair<string, string>("grant_type", "client_credentials")
});
var response = await client.PostAsync(url, content);
var json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
var token = doc.RootElement.GetProperty("access_token").GetString()!;
Console.WriteLine("Access token retrieved successfully: {0}...", token.Substring(0, 20));
return token;
}
This access token is scoped to 24 hours so you don't need to re-generate it at every request.
Getting user id
You need the user id to create the Teams meeting for. You can either retrieve it manually from Entra or use the following code to display them all:
public record User(string Id, string DisplayName, string UserPrincipalName)
{
public override string ToString()
{
return $"{DisplayName} ({Id}) - {UserPrincipalName}";
}
}
public static async Task<List<User>> ListUsers(string accessToken)
{
List<User> users = [];
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await client.GetAsync("https://graph.microsoft.com/v1.0/users");
var json = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
Console.WriteLine("Failed to retrieve users:");
Console.WriteLine(json);
return users;
}
using var doc = JsonDocument.Parse(json);
foreach (var user in doc.RootElement.GetProperty("value").EnumerateArray())
{
var id = user.GetProperty("id").GetString();
var displayName = user.GetProperty("displayName").GetString();
var userPrincipalName = user.GetProperty("userPrincipalName").GetString();
users.Add(new User(id!, displayName!, userPrincipalName!));
}
return users;
}
Note that if this code errors out you may need to add more permissions to you registered app.
Creating The Meeting
So it has all come to this. Here's the code to create the actual meeting:
public async Task<string> CreateMeeting(User user, string accessToken, string subject)
{
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var requestUrl = $"https://graph.microsoft.com/v1.0/users/{user.Id}/onlineMeetings";
var startDateTime = DateTime.UtcNow.AddMinutes(5).ToString("o");
var endDateTime = DateTime.UtcNow.AddMinutes(30).ToString("o");
var meetingRequest = new { startDateTime, endDateTime, subject };
var serialized = JsonSerializer.Serialize(meetingRequest);
var jsonContent = new StringContent(serialized, Encoding.UTF8, "application/json");
var response = await client.PostAsync(requestUrl, jsonContent);
var json = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
Console.WriteLine("Error creating meeting:");
Console.WriteLine(json);
return string.Empty;
}
using var doc = JsonDocument.Parse(json);
return doc.RootElement.GetProperty("joinWebUrl").GetString()!;
}
What this code returns is a link to the actual meeting you can pass to anyone to use in their browser os teams client.