Developing Web SDK applications for Skype for Business Server
Applies to: Skype for Business
In this article
Initialize the Skype application object
Password grant authentication
Integrated Windows Authentication (IWA)
Anonymous meeting join
Passive/ADFS authentication
OAuth2 authentication
This section shows how to develop a Skype Web SDK client application for Skype for Business Server.
Download the SDK and sign in
Add the following code to the document function of an index page in your app.
Skype.initialize({
apiKey: 'a42fcebd-5b43-4b89-a065-74450fb91255' // SDK preview
}, api => {
var app = new api.application;
app.signInManager.signIn(...).then(() => {
console.log("signed in as", app.personsAndGroupsManager.mePerson.displayName());
}, err => {
console.log("cannot sign in", err);
});
}, err => {
console.log("cannot download the SDK", err);
});
Password grant authentication
In the password grant authentication flow, the SDK sends username and password to the server to get a web ticket:
app.signInManager.signIn({
username: 'user123@contoso.com',
password: '17Psnds732'
});
If no other parameters are provided, the SDK extracts the domain name from the username and attempts to discover UCWA with the two GETs:
GET https://lyncdiscover.contoso.com
GET https://lyncdiscoverinternal.contoso.com
The two URLs are also known as root
URLs. One of the two servers is supposed to exist and have a valid certificate. If this is not possible, but you know or can discover by other means the root
URLs, they can be given in the origins parameter:
app.signInManager.signIn({
username: '****',
password: '****',
origins: [
"https://sfbweb1.contoso.com/autodiscover/autodiscoverservice.svc/root",
"https://sfbweb2.contoso.com/autodiscover/autodiscoverservice.svc/root"
]
});
This will tell the SDK to send GETs to these URLs instead:
GET https://sfbweb1.contoso.com/autodiscover/autodiscoverservice.svc/root
GET https://sfbweb2.contoso.com/autodiscover/autodiscoverservice.svc/root
GETs to root
URLs do not require authentication. A GET to a root
URL returns a so called user
URL which does require authentication:
GET https://sfbweb1.contoso.com/autodiscover/autodiscoverservice.svc/root
HTTP 200
{ "_links": { "user": { href: "https://sfbweb1.contoso.com/autodiscover/autodiscoverservice.svc/user" } } }
If the user
URL is known beforehand, it can be useful in some cases to start the discovery process from it:
app.signInManager.signIn({
username: '****',
password: '****',
root: { user: "https://sfbweb1.contoso.com/autodiscover/autodiscoverservice.svc/user" }
});
The SDK proceeds with a GET to the user
URL and gets back a 401
response. Most browsers log such responses in the dev console.
GET https://sfbweb1.contoso.com/autodiscover/autodiscoverservice.svc/user
HTTP 401
WWW-Authenticate: MsRtcOAuth grant_type="password"
The SDK checks that in the 401 response the server says that it supports the password grant auth. Then the SDK sends a POST request with username and password to get a web ticket and resends the GET /user request with the web ticket. The response is supposed to have a so called applications
URL which is also known as UCWA URL, because this is when the UCWA service is hosted.
GET https://sfbweb1.contoso.com/autodiscover/autodiscoverservice.svc/user
Authorization: Bearer cwt=AAB...
HTTP 200
{ "_links": { "user": { href: "https://sfbwebfes0b0m.infra.contoso.com/.../applications" } } }
If the applications
URL is known beforehand, it can be useful in some cases to skip the discovery process and go to that URL directly. This URL usually changes even for the same user.
app.signInManager.signIn({
username: '****',
password: '****',
snapshot: { applications: "https://sfbwebfes0b0m.infra.contoso.com/.../applications" }
});
More often than not the applications
URL belongs to a different domain. This happens because before the GET /user request, the server doesn't know who the user is and thus cannot know where the user data is hosted; but after the GET request, the server gets the user identity in the web ticket, discovers the server where the user is homed, and returns a URL to that server.
Web tickets are usually issued for a specific server domain and cannot be used to access a different server. This is why when the SDK attempts to access the applications
URL, it gets an authorization-related error (can be a 401, 403 or 500) and another 4xx/5xx response gets printed to the dev console by the browser. Once the SDK gets another web ticket for the new server FQDN, it sends a POST /applications to create a UCWA endpoint:
POST https://sfbwebfes0b0m.infra.contoso.com/.../applications
Authorization: Bearer cwt=AAC...
HTTP 201
{ "rel": "application", "_links": { "self": { href: "/.../application" } } }
Once this 201 is received, the sign-in operation is considered to be done and the promise object returned by the signIn method is resolved.
app.signInManager.signIn(...).then(() => {
console.log("POST /applications has returned a 2xx");
});
Integrated Windows Authentication (IWA)
To sign in using the Integrated Windows Authentication (IWA) flow, provide the domain parameter:
app.signInManager.signIn({
domain: 'contoso.com'
});
The SDK will start with GETs to the lyncdiscover URLs as described above and will use the intergrated auth to get a web ticket. In this mode the SDK doesn't have access to username and password as they are managed by the browser and the operating system.
This auth mode is enabled when 401 responses have urn:microsoft.rtc:window
in the WWW-Authenticate.MsRtcOAuth.grant_type
setting.
Anonymous meeting join
To sign in using the anonymous meeting join flow, your application needs to provide the URI of the online meeting:
app.signInManager.signIn({
meeting: 'sip:user123@contoso.com;gruu;opaque=app:conf:focus:id:AHSJDNA'
});
The SDK will extract the FQDN from the conference URI and will use it to construct the lyncdiscover
requests. To get a web ticket, the SDK will extract the conference key from the URI (AHSJDNA
in this example). The discovery process can be customized as described above.
This auth mode is enabled when 401 responses have urn:microsoft.rtc:anonmeeting
in the WWW-Authenticate.MsRtcOAuth.grant_type
setting.
Passive/ADFS authentication
You can use the passive authentication if your on-premises server has ADFS configured. To use this auth, set the auth
param:
app.signInManager.signIn({
auth: "passive",
domain: "contoso.com"
});
Prior to calling the signIn method, the user needs to enter credentials at the ADFS site. Once this is done, the site sends a few auth cookies that get stored in the browser's cache. When the SDK gets a web ticket in this mode, it creates a hidden <iframe>
element, redirects it to the ADFS site and gets back an RPSAuth
cookie, which is then exchanged for a web ticket. This authnetication will not work in Safari because it blocks third-party (the ADFS site in this case) cookies by default. For the same reason, this might not work in IE if the ADFS site and your site belong to different trusted zones in the user's instance of IE. If something goes wrong, the SDK won't have a way to detect the failure and thus won't be able to reject the returned promise object. The caller is supposed to be aware of these specifics of ADFS auth and set proper timeouts.
The discovery process can be customized as described above.
This auth mode is enabled when 401 responses have urn:microsoft.rtc:passive
in the WWW-Authenticate.MsRtcOAuth.grant_type
setting.
OAuth2 authentication
To use this authentication, set client_id
of your registered app:
app.signInManager.signIn({
cors: true,
client_id: "...",
redirect_uri: "/an/empty/page/on/this/site.html",
origins: [...]
});
The user is supposed to be logged in at the OAuth provider beforehand. The SDK uses a hidden <iframe>
element to send the OAuth2 request and if the user isn't logged in, the promise object returned by signIn
will be rejected with an error:
{ code: "OAuthFailed", error: "login_required", error_description: "..." }
The discovery process can be customized as described above.