azureoffice365skype-for-businessucwaskypedeveloper

Sending IM with Skype for Business Online from Console App


I am trying to set up a C# console app that can send notifications/reminders to users via Skype for Business online from a generic AD account. I was excited to see the other day that according to this page, UCWA is now supported in Skype for Business online: https://msdn.microsoft.com/en-us/library/office/mt650889.aspx.

I've been trying to follow this tutorial to get this set up: https://msdn.microsoft.com/en-us/library/office/mt590891(v=office.16).aspx. So far I haven't really had much luck... I have my application set up in Azure AD but I get stuck at the "Requesting an access token using implicit grant flow" step of that article (not 100% certain I'm taking the correct actions before that either)... so far I have this:

        string clientId = "xxxxxxxx"
        string resourceUri = "https://webdir.online.lync.com";
        string authorityUri = "https://login.windows.net/common/oauth2/authorize";
        AuthenticationContext authContext = new AuthenticationContext(authorityUri);
        UserCredential cred = new UserCredential("username", "password");
        string token = authContext.AcquireToken(resourceUri, clientId, cred).AccessToken;


        var poolReq = CreateRequest("https://webdir.online.lync.com/autodiscover/autodiscoverservice.svc/root", "GET",token);
        var poolResp = GetResponse(poolReq);

        dynamic tmp = JsonConvert.DeserializeObject(poolResp);
        string resourcePool = tmp._links.user.href;

        Console.WriteLine(resourcePool);

        var accessTokenReq = CreateRequest("https://login.windows.net/common/oauth2/authorize"
            + "?response_type=id_token"
            + "&client_id=" + clientId
            + "&redirect_uri=https://login.live.com/oauth20_desktop.srf"
            + "&state=" + Guid.NewGuid().ToString()
            + "&resource=" + new Uri(resourcePool).Host.ToString()
            , "GET",token);
        var accessTokenResp = GetResponse(accessTokenReq);

my GetResponse and CreateRequest methods:

    public static string GetResponse(HttpWebRequest request)
    {
        string response = string.Empty;

        using (HttpWebResponse httpResponse = request.GetResponse() as System.Net.HttpWebResponse)
        {
            //Get StreamReader that holds the response stream
            using (StreamReader reader = new System.IO.StreamReader(httpResponse.GetResponseStream()))
            {
                response = reader.ReadToEnd();
            }
        }

        return response;
    }



    public static HttpWebRequest CreateRequest(string uri, string method, string accessToken)
    {
        HttpWebRequest request = System.Net.WebRequest.Create(uri) as System.Net.HttpWebRequest;
        request.KeepAlive = true;
        request.Method = method;
        request.ContentLength = 0;
        request.ContentType = "application/json";
        request.Headers.Add("Authorization", String.Format("Bearer {0}", accessToken));

        return request;
    }

accessTokenResp is an office online logon page, not the access token I need to move forward... so I'm stuck. I've tried quite a few variations of the above code.

I've been scouring the net for more examples but can't really find any, especially since UCWA support for Office 365 is so new. Does anyone have an example of how to do what I am trying to do or can point me to one? Everything I've found so far hasn't really even been close to what I'm trying. I can't use the Skype for Business client SDK unfortunately either as it doesn't meet all of my requirements.


Solution

  • I came to a working solution using ADAL (v3), with the help of steps outlined at Authentication using Azure AD

    Here the steps, which involve requesting multiple authentication tokens to AAD using ADAL

    Voilá, your UCWA application is created. What I notice at the moment, is that just few resources are available, excluding me / presence. So users' presence can be retrieved, but self presence status can't be changed. I've been able however to retrieve my personal note, and the following resources are available to me:

    Show me some code:

    Function to perform the flow obtaining and switching auth tokens

    public static async Task<UcwaApp> Create365UcwaApp(UcwaAppSettings appSettings, Func<string, Task<OAuthToken>> acquireTokenFunc)
    {
        var result = new UcwaApp();
        result.Settings = appSettings;
    
        var rootResource = await result.Discover365RootResourceAsync(appSettings.DomainName);
        var userUri = new Uri(rootResource.Resource.GetLinkUri("user"), UriKind.Absolute);
        //Acquire a token for the domain where user resource is
        var token = await acquireTokenFunc(userUri.GetComponents(UriComponents.SchemeAndServer, UriFormat.SafeUnescaped));
        //Set Authorization Header with new token
        result.AuthToken = token;
        var usersResult = await result.GetUserResource(userUri.ToString());
        //
        result.ApplicationsUrl = usersResult.Resource.GetLinkUri("applications");
        var appsHostUri = new Uri(result.ApplicationsUrl, UriKind.Absolute).GetComponents(UriComponents.SchemeAndServer, UriFormat.SafeUnescaped);
        //Acquire a token for the domain where applications resource is
        token = await acquireTokenFunc(appsHostUri);
        //Set Authorization Header with new token
        result.AuthToken = token;
        //
        var appResult = await result.CreateApplicationAsync(result.ApplicationsUrl, appSettings.ApplicationId, appSettings.UserAgent, appSettings.Culture);
    
        return result;
    }
    

    Usage code ato retrieve OAuth tokens using ADAL

    var ucSettings = new UcwaAppSettings
    {
        UserAgent = "Test Console",
        Culture = "en-us",
        DomainName = "yourdomain.onmicrosoft.com",
        ApplicationId = "your app client id"
    };
    
    var acquireTokenFunc = new Func<string, Task<OAuthToken>>(async (resourceUri) =>
    {
       var authContext = new AuthenticationContext("https://login.windows.net/" + ucSettings.DomainName);
    
       var ar = await authContext.AcquireTokenAsync(resourceUri,
                    ucSettings.ApplicationId,
                    new UserCredential("myusername", "mypassword"));
    
    
       return new OAuthToken(ar.AccessTokenType, ar.AccessToken, ar.ExpiresOn.Ticks);
    });
    
    var app = await UcwaApp.Create365UcwaApp(ucSettings, acquireTokenFunc);
    

    It should be of course possible to avoid hard-coding username and password using ADAL, but this was easier for PoC and especially in case of Console Application as you asked