Search code examples
c#desire2learnvalence

D2L Valence: Auto-authentication in c# with app & user id/key


the below is my code.

I'm building a c# window app to obtain some information from url without having to log in but auto-log in.

It's window app form that will output course offerings when user clicks the accept button. This code is based on valence-client-side sample code. I want this app to log in with the app id/key pair and user id/key pair and get course offerings and output them. But, when I run this program, it just stops var ctx = httpListener.GetContext(); at this line. I don't want it to open a browser but want to get auto-log in with app and user id/key pair in c#, and get json response from the url. so users do not have to log in.

namespace CourseOfferingWindow
{
    class CourseOfferingResponse
{
    public string Identifier { get; set; }
    public string Name { get; set; }
    public string Code { get; set; }
    public bool IsActive { get; set; }
    public string Path { get; set; }
    public object StartDate { get; set; }
    public object EndDate { get; set; }
    public string CourseTemplate { get; set; }
    public string Semester { get; set; }
    public string Department { get; set; }
}

public partial class CourseOfferingWindowForm : Form
{
    public CourseOfferingWindowForm()
    {
        InitializeComponent();
    }

    private static ID2LUserContext InterceptUserTokens(HostSpec host, ID2LAppContext appContext) 
    {
        // Start HTTP server and listen for the redirect after a successful auth
        var httpListener = new HttpListener();
        httpListener.Prefixes.Add("http://localhost:31337/result/");
        httpListener.Start();

        // This call blocks until we get a response
        var ctx = httpListener.GetContext();

        // The LMS returns the user tokens via query parameters to the value provided originally in x_target
        // TODO: deal with "failed to login" case
        var userContext = appContext.CreateUserContext(ctx.Request.Url, host);

        // Send some JavaScript to close the browser popup
        // This is not 100% effective: for example, Firefox will ignore this.
        const string RESPONSE = "<!doctype html><meta charset=\"utf-8\"><script>window.close();</script><h1>You may now close your window</h1><p>You may or may not see this message, depending on your browser</p>";
        byte[] buffer = System.Text.Encoding.UTF8.GetBytes(RESPONSE);
        ctx.Response.ContentType = "text/html";
        ctx.Response.ContentLength64 = buffer.Length;
        ctx.Response.OutputStream.Write(buffer, 0, buffer.Length);
        ctx.Response.OutputStream.Close();
        httpListener.Stop();

        return userContext;
    }


    private static void DoApiStuff(string host, ID2LUserContext userContext)
    {
        const string COURSEOFFERING_ROUTE = "/d2l/api/lp/1.0/courses/644849";

        var client = new RestClient(host);
        var valenceAuthenticator = new D2L.Extensibility.AuthSdk.Restsharp.ValenceAuthenticator(userContext);
        var request = new RestRequest(COURSEOFFERING_ROUTE, Method.GET);
        valenceAuthenticator.Authenticate(client, request);

        var response = client.Execute<CourseOfferingResponse>(request);

        Console.WriteLine("Hello, " + {course offerings information} );
    }

    private void ButtonAccept_Click(object sender, EventArgs e)
    {   

        // This is the LMS we will interact with
        var host = new HostSpec("https", "www.foltest.ca", 443);

        // The appId/appKey come from our app.config - it is good to seperate access keys from the code that uses them.
        // Ideally you wouldn't have production keys committed to source control.
        string appId = ConfigurationManager.AppSettings["appId"];
        string appKey = ConfigurationManager.AppSettings["appKey"];

        // This is the port we will temporarily host a server on to intercept the user tokens after a successful login
        int port = int.Parse(ConfigurationManager.AppSettings["serverPort"]);

        // Create url for the user to login. If they have already done so they will not actually have to type their password (maybe).
        var appContextFactory = new D2LAppContextFactory();
        var appContext = appContextFactory.Create(appId, appKey);
        var authUrl = appContext.CreateUrlForAuthentication(host, new Uri("http://localhost:" + port + "/result/"));

        //OpenBrowser(authUrl);

        // This call will block until we have a result
        // TODO: you'll want better control flow and error handling here
        var userContext = InterceptUserTokens(host, appContext);

        // Now we can call Valence
        DoApiStuff(host.Scheme + "://" + host.Host + ":" + host.Port, userContext);

        // Pause the terminal
        Console.ReadKey();

    }


}

}

any kind of help will be appreciated. Thanks, Phillip


Solution

  • Instead of:

    var authUrl = appContext.CreateUrlForAuthentication(host, new Uri("http://localhost:" + port + "/result/"));
    
    //OpenBrowser(authUrl);
    
    // call will block until we have a result
    // TODO: you'll want better control flow and error handling here
    var userContext = InterceptUserTokens(host, appContext);
    
    // Now we can call Valence
    DoApiStuff(host.Scheme + "://" + host.Host + ":" + host.Port, userContext);
    

    do:

    const string userId = "a_user_id"; // Use the correct user id
    const string userKey = "a_user_key"; // Use the correct user key
    var userContext = appContext.CreateUserContext(userId, userKey, host);
    DoApiStuff(host.Scheme + "://" + host.Host + ":" + host.Port, userContext);
    

    This assumes you have a Valence user id and key for the user account you want to use at your disposal. If you don't, you need to generate them out-of-band.