Skype for Business Development Platform provides the various developer experiences, but there’s been no way to develop server-side application with online SKUs for a long time. (With server SKUs, you can use existing UCMA for the server-side endpoint programming.)
Now you can use the Trusted Application API for this sort of server-side development with Office 365. (Now in preview.)
This brand-new Trusted Application API covers the following server-side (backend) scenarios for developers. (See “MSDN : Trusted Application API” for details.)
Especially, you can assign the PSTN phone number to this trusted endpoint, and then you can provide the telephony solutions like IVR (Interactive Voice Response) for all users who is not having the Skype client.
Trusted Application API is not just for the bot, but including a lot of server-side powerful scenarios.
- Bots and Notifications
- Anonymous Customer Web Chat
- PSTN audio conferencing
(IVR to join the conference, in-meeting Personal Virtual Assistant, and in-meeting announcements) - Service-side meeting recording
- Inbound/outbound IVRs
- Helpdesk
- Expert-finder
- Customer engagement / Contact Center
In this post, we focus on the authentication and online meetings for your first start.
Later I will show you the simple programming code with SDK, but first let’s look at the quick view of HTTP flows, because it helps you understand how it works on the bottom and trouble-shootings.
Endpoint Registration
Before building your applications, you must register your app (endpoint) in Azure AD (Azure Active Directory) and Skype for Business Online Trusted Application platform.
First you should go to Skype for Business Online Application Registration Portal, login with Office 365 admin account, fill the settings, and create your application. This application is registered in your Azure AD tenant. (You can see the registered application in Azure Portal without Azure subscription.)
Trusted Application API uses the application context token instead of the user context (see “Azure AD – Backend server-side application” in my early post). Then please select the appropriate application permissions (not delegated permissions) according to your application’s functionalities in the portal. (see the following screenshot)
For example, if your application handles the online meeting capabilities, select only [Create on-demand Skype meetings], [Join and Manage Skype Meetings], and [Guest user join services] in Application Registration Portal.
After you created, please copy the generated application id and client secret (key).
Note that you must create (or setup) your application using Skype for Business Online Application Registration Portal, not using Azure Portal. If you have already registered your application with Azure Portal, make sure to set up with Skype for Business Online Application Registration Portal again. (see the following screenshot)
Because the application must be stored in Skype for Business Online platform.
After you complete the application registration, you must access the following url with your Office 365 administrator account, and consent this application as administrator.
Please replace the following {application id} and {sign-on url} for your appropriate values. (Note that the all values must be url-encoded.)
As you can see, the resource id of Trusted Application API is https://noammeetings.resources.lync.com
.
https://login.windows.net/common/oauth2/authorize?response_type=id_token
&client_id={application id}
&redirect_uri={sign-on url}
&response_mode=form_post
&nonce=123456
&resource=https%3A%2F%2Fnoammeetings.resources.lync.com
&prompt=admin_consent
When you login with admin account, the following consent UI is displayed. Please accept this consent.
Next you must register the trusted application endpoint in your Skype for Business Online Trusted Application platform.
Before doing that (registration), please download and install Skype for Business Online Windows PowerShell Module in your Windows client beforehand.
After the installation is done, launch PowerShell and run the following commands.
$cr = Get-Credential
# It prompts login. Enter your admin user id and password.
$session = New-CsOnlineSession -Credential $cr
Import-PSSession $session
New-CsOnlineApplicationEndpoint `
-Uri "sip:{arbitrary unique name}@{your domain prefix}.onmicrosoft.com" `
-ApplicationId "{application id}" `
-Name "{your app name}" `
-Tenant "{your tenant id}"
# If you get tenant id, please input the following
# $tenantId = (Get-MsolCompanyInformation).objectId
# echo $tenantId
Here’s my example.
$cr = Get-Credential
# It prompts login. Enter your admin user id and password.
$session = New-CsOnlineSession -Credential $cr
Import-PSSession $session
New-CsOnlineApplicationEndpoint `
-Uri "sip:trustedapidemo01@mod776816.onmicrosoft.com" `
-ApplicationId "d4daaf71-5f06-4f70-bf4b-418a97f34741" `
-Name "TrustedApiTest01" `
-Tenant "3bc5ea6c-9286-4ca9-8c1a-1b2c4f013f15"
As I described earlier, you can assign PSTN phone number to trusted endpoint, but here I skip this step.
HTTP Flow – Authentication and Initialization
Trusted Application API uses the application context token, not user context token. (Not needed to display login UI and user’s login.)
As I explained in my early post “Azure AD – Backend server-side application“, we can get the application context token (access token) for the Trusted Application API (https://NOAMmeetings.resources.lync.com
) with the following HTTP request.
Please note that you need the tenant-aware uri (https://login.microsoftonline.com/{your tenant realm}/oauth2/token
) as follows.
POST https://login.microsoftonline.com/mod776816.onmicrosoft.com/oauth2/token
Accept: application/json
Content-Type: application/x-www-form-urlencoded
resource=https%3A%2F%2FNOAMmeetings.resources.lync.com&
client_id=d4daaf71-5f06-4f70-bf4b-418a97f34741&
client_secret=kEQ2B1rCs...&
grant_type=client_credentials
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"token_type": "Bearer",
"expires_in": "3600",
"ext_expires_in": "262800",
"expires_on": "1492076011",
"not_before": "1492072111",
"resource": "https://NOAMmeetings.resources.lync.com",
"access_token": "eyJ0eXAiOi..."
}
After you’ve got access token, first we ask the the Trusted Application endpoint url for the discovery service. (You must set the retrieved access token in the Authorization HTTP header as follows.)
GET https://api.skypeforbusiness.com/platformservice/discover
Authorization: Bearer eyJ0eXAiOi...
Accept: application/json; charset=utf-8
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"_links": {
"self": {
"href": "https://api.skypeforbusiness.com:4443/platformservice/discover"
},
"service:applications": {
"href": "https://ring2noammeetings.resources.lync.com/platformService/v1/applications"
},
"myApplications": {
"href": "https://ring2noammeetings.resources.lync.com/platformService/v1/myApplications"
}
},
"rel": "service:discover"
}
Next you can get several endpoints for each activities as following steps.
- Get the access url for your sip application
- Get all endpoints for your activities (resources for ad-hoc meeting, for anonymous joining token, for messaging, etc)
The following is getting the access url for my sip application. Please replace endpointId
(sip:trustedapidemo01@mod776816.onmicrosoft.com
) with your app’s sip id.
GET https://ring2noammeetings.resources.lync.com/platformService/v1/applications?endpointId=sip:trustedapidemo01@mod776816.onmicrosoft.com
Authorization: Bearer eyJ0eXAiOi...
Accept: application/json; charset=utf-8
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"_links": {
"self": {
"href": "/platformservice/v1/applications"
},
"service:application": {
"href": "/platformservice/v1/applications/3877116191?endpointId=sip%3atrustedapidemo01%40mod776816.onmicrosoft.com"
}
},
"rel": "service:applications"
}
The following is retrieving the each endpoints for each resources (resources for ad-hoc meeting, for anonymous joining token, for messaging, etc).
For example, if your app creates new ad-hoc meeting, use /platformservice/v1/applications/3877116191/adhocMeetings
for calling endpoint.
GET https://ring2noammeetings.resources.lync.com/platformservice/v1/applications/3877116191?endpointId=sip:trustedapidemo01%40mod776816.onmicrosoft.com
Authorization: Bearer eyJ0eXAiOi...
Accept: application/json; charset=utf-8
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"_links": {
"self": {
"href": "/platformservice/v1/applications/3877116191?endpointId=sip%3atrustedapidemo01%40mod776816.onmicrosoft.com"
},
"service:anonApplicationTokens": {
"href": "/platformservice/v1/applications/3877116191/anonApplicationTokens?endpointId=sip:trustedapidemo01@mod776816.onmicrosoft.com"
}
},
"_embedded": {
"service:communication": {
"_links": {
"self": {
"href": "/platformservice/v1/applications/3877116191/communication?endpointId=sip:trustedapidemo01@mod776816.onmicrosoft.com"
},
"service:joinOnlineMeeting": {
"href": "/platformservice/v1/applications/3877116191/communication/onlineMeetingInvitations?endpointId=sip:trustedapidemo01@mod776816.onmicrosoft.com"
},
"service:inviteUserToMeeting": {
"href": "/platformservice/v1/applications/3877116191/communication/userMeetingInvitations?endpointId=sip:trustedapidemo01@mod776816.onmicrosoft.com"
},
"service:startMessaging": {
"href": "/platformservice/v1/applications/3877116191/communication/messagingInvitations?endpointId=sip:trustedapidemo01@mod776816.onmicrosoft.com"
},
"service:startAudioVideo": {
"href": "/platformservice/v1/applications/3877116191/communication/audioVideoInvitations?modalities=AudioVideou0026endpointId=sip:trustedapidemo01@mod776816.onmicrosoft.com"
},
"service:startAudio": {
"href": "/platformservice/v1/applications/3877116191/communication/audioVideoInvitations?modalities=Audiou0026endpointId=sip:trustedapidemo01@mod776816.onmicrosoft.com"
}
},
"rel": "service:communication",
"etag": "4294967295"
},
"myOnlineMeetings": {
"_links": {
"self": {
"href": "/platformservice/v1/applications/3877116191/myOnlineMeetings?endpointId=sip:trustedapidemo01@mod776816.onmicrosoft.com"
}
},
"rel": "myOnlineMeetings"
},
"service:adhocMeetings": {
"_links": {
"self": {
"href": "/platformservice/v1/applications/3877116191/adhocMeetings?endpointId=sip:trustedapidemo01@mod776816.onmicrosoft.com"
}
},
"rel": "service:adhocMeetings"
}
},
"rel": "service:application"
}
HTTP Flow – Interact with Online Meeting
The schema of Trusted Application endpoint is based on existing UCWA schema. If you’re familiar with UCWA, you can easily understand the envelop of trusted application’s HTTP flow.
Let’s see using the ad-hoc meeting examples.
Skype for Business developer platform is providing the B2C scenario like “staff-customers”, “doctor-patients”, and “lawyer-clients” using the Skype guest online meeting join. (See the document of Skype for Business App SDK or Skype Web SDK in MSDN.)
In this scenario, the application must provide the following steps.
- When it’s called, the application creates the ad-hoc meeting in the backend.
- The staff joins in this generated meeting as Skype users with Skype client or Skype Web SDK etc.
- On the other hand, the customer joins as the guest account (as anonymous user) without Skype license. The application must provide the token for anonymous join, and Skype Web SDK / Skype for Business App SDK will provide the user interface for customers. (They don’t have Skype client.)
As you can see, the backend api helps several tasks in this scenario. (For instance, creating ad-hoc meeting, providing the token for anonymous join, etc)
Now here I describe how you can leverage Trusted Application API along with this scenario.
For example, the following is creating new ad-hoc meeting with Trusted Application API. The uri fragment of /platformservice/v1/applications/3877116191/adhocMeetings
is the previously retrieved uri.
The retrieved joinUrl
is the meeting url. For instance, you can join the meeting by accessing this url with your Web browser.
POST https://ring2noammeetings.resources.lync.com/platformservice/v1/applications/3877116191/adhocMeetings?endpointId=sip:trustedapidemo01@mod776816.onmicrosoft.com
Authorization: Bearer eyJ0eXAiOi...
Content-Type: application/vnd.microsoft.com.ucwa+json; charset=utf-8
{
"subject": "meeting01",
"accessLevel": "Everyone",
"rel": "service:meeting"
}
HTTP/1.1 200 OK
Content-Type: application/vnd.microsoft.com.ucwa+json; charset=utf-8
ETag: "4221088333"
{
"accessLevel": "Everyone",
"entryExitAnnouncement": "Disabled",
"automaticLeaderAssignment": "Disabled",
"description": "",
"expirationTime": "/Date(1492106326000)/",
"onlineMeetingId": "PYQC0NM0",
"onlineMeetingUri": "sip:DM20R04meet1467@noammeetings.lync.com;gruu;opaque=app:conf:focus:id:PYQC0NM0",
"organizerUri": "sip:DM20R04meet1467@noammeetings.lync.com",
"conferenceId": "PYQC0NM0",
"phoneUserAdmission": "Disabled",
"lobbyBypassForPhoneUsers": "Disabled",
"subject": "meeting01",
"joinUrl": "https://meet.resources.lync.com/NOAMmeetings/dm20r04meet1467/PYQC0NM0",
"_links": {
"self": {
"href": "/platformservice/v1/applications/3877116191/adhocMeetings/PYQC0NM0?endpointId=sip:trustedapidemo01@mod776816.onmicrosoft.comu0026onlineMeetingContext=sip:DM20R04meet1467@noammeetings.lync.com"
},
"service:discover": {
"href": "https://noammeetings.resources.lync.com/platformService/discover?discoverContext=HSAUbe4hAr..."
},
"service:joinAdhocMeeting": {
"href": "https://webpooldm20r04.infra.lync.com/platformservice/v1/applications/3877116191/communication/onlineMeetingInvitations?confUrl=sip:DM20R04meet1467@noammeetings.lync.com;gruu;opaque=app:conf:focus:id:PYQC0NM0u0026endpointId=sip:trustedapidemo01@mod776816.onmicrosoft.com"
},
"applications": {
"href": "https://webpooldm20r04.infra.lync.com/ucwa/v1/applications"
}
},
"rel": "service:adhocMeeting",
"etag": "4221088333"
}
Next example is retrieving the anonymous token for this meeting.
POST https://ring2noammeetings.resources.lync.com/platformservice/v1/applications/3877116191/anonApplicationTokens?endpointId=sip:trustedapidemo01@mod776816.onmicrosoft.com
Authorization: Bearer eyJ0eXAiOi...
Content-Type: application/vnd.microsoft.com.ucwa+json; charset=utf-8
{
"meetingUrl": "https://meet.resources.lync.com/NOAMmeetings/dm20r04meet1467/PYQC0NM0",
"allowedOrigins": "https://contoso.com/callback",
"applicationSessionId": "test0001"
}
HTTP/1.1 200 OK
Content-Type: application/vnd.microsoft.com.ucwa+json; charset=utf-8
{
"token": "psat=eyJ0eXAiOiJKV1QiLCJh...",
"expiryTime": "/Date(1492108195460)/",
"_links": {
"self": {
"href": "https://ring2noammeetings.resources.lync.com:4443/platformservice/v1/applications/3877116191/anonApplicationTokens?endpointId=sip:trustedapidemo01@mod776816.onmicrosoft.com"
},
"service:discover": {
"href": "https://noammeetings.resources.lync.com/platformService/discover?anonymousMeetingJoinContext=psat%253deyJ0eXAiOiJKV1QiLCJh..."
}
},
"rel": "service:anonApplicationToken"
}
Once the user gets the anonymous token, the user can join the meeting using Skype Web SDK (without Skype for Business client) as follows. (See “Introduction for Skype Web SDK” in my early post for the programming details.)
app.signInManager.signIn(
{
"name": "meeting01",
"token": "Bearer psat=eyJ0eXAiOi...",
"root": {
"user": "https://noammeetings.resources.lync.com/platformService/discover?anonymousMeetingJoinContext=psat%253deyJ0eXAiOi..."
},
"cors": true
}
).then(function () {
...
}, function (err) {
...
});
Note : Sorry, but currently the internal error (PlatformService.Web.PlatformServiceWebException, etc) will frequently occur and it’s difficult to solve the reason of error, when your request is not valid. (For example, when you access the expired meeting, etc…)
SDK for Trusted Application API
If you’re .NET programmer, you can use the .NET SDK for calling Trusted Application API.
You just install the following NuGet package in your project. (Note that .NET 4.6.2 is needed, and select [include prerelease] like the following screenshot.)
Microsoft.SkypeforBusiness.TrustedApplicationAPI.SDK
Microsoft.SkypeforBusiness.TrustedApplicationAPI.ResourceContact
The following is the C# programming example of the previous HTTP flow (including authentication, initialization, ad-hoc meeting creation, and getting anonymous token).
Sorry, but it’s classic Win Form application …
...
using Microsoft.SfB.PlatformService.SDK.Common;
using Microsoft.SfB.PlatformService.SDK.ClientModel;
...
public ClientPlatformSettings platsettings = null;
public ApplicationEndpoint appendpoint { get; set; }
private async void Initialize()
{
platsettings = new ClientPlatformSettings(
"kEQ2B1rCsf...", // secret (key)
Guid.Parse("d4daaf71-5f06-4f70-bf4b-418a97f34741")); // application id
var clplatform = new ClientPlatform(
platsettings,
new MyLogger());
var endpointsettings = new ApplicationEndpointSettings(
new SipUri("sip:trustedapidemo01@mod776816.onmicrosoft.com")); // sip uri
appendpoint = new ApplicationEndpoint(
clplatform,
endpointsettings,
null);
await appendpoint.InitializeAsync().
ConfigureAwait(false);
await appendpoint.InitializeApplicationAsync().
ConfigureAwait(false);
MessageBox.Show("done");
}
private async void CreateAdhocMeeting()
{
var createarg = new AdhocMeetingCreationInput("meeting01");
var adhocmeetingResources =
await appendpoint.Application.CreateAdhocMeetingAsync(createarg)
.ConfigureAwait(false);
MessageBox.Show("done");
}
private async void GetAnonymousToken()
{
var tokenResources =
await appendpoint.Application.GetAnonApplicationTokenForMeetingAsync(
@"https://meet.resources.lync.com/NOAMmeetings/dm20r04meet1467/PYQC0NM0",
@"https://contoso.com/callback",
"test0001").ConfigureAwait(false);
MessageBox.Show("done");
}
public class MyLogger : IPlatformServiceLogger
{
public bool HttpRequestResponseNeedsToBeLogged { get; set; }
public void Information(string message)
{
MessageBox.Show($"[INFO]{message}");
}
public void Information(string fmt, params object[] vars)
{
MessageBox.Show($"[INFO]{string.Format(fmt, vars)}");
}
public void Information(Exception exception, string fmt, params object[] vars)
{
MessageBox.Show($"[INFO]{string.Format(fmt, vars)}");
}
public void Warning(string message)
{
MessageBox.Show($"[WARN]{message}");
}
public void Warning(string fmt, params object[] vars)
{
MessageBox.Show($"[WARN]{string.Format(fmt, vars)}");
}
public void Warning(Exception exception, string fmt, params object[] vars)
{
MessageBox.Show($"[WARN]{string.Format(fmt, vars)}");
}
public void Error(string message)
{
MessageBox.Show($"[ERR]{message}");
}
public void Error(string fmt, params object[] vars)
{
MessageBox.Show($"[ERR]{string.Format(fmt, vars)}");
}
public void Error(Exception exception, string fmt, params object[] vars)
{
MessageBox.Show($"[ERR]{string.Format(fmt, vars)}");
}
}
Reference : early posts (all Japanese) for “Skype for Business” developers