cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
andrews2
Community Member

Javascript access to API via access_token

Jump to solution

Hi,

I'm trying to make a web app that should interface with canvas data via the API. I know that eventually I need to get it working with the OAuth system, but for development I wanted to get it working with an access token. My code looks like the below (essentially copied from another thread here), but it gives a failed to fetch error.

In another tab on the browser, I am logged into canvas. 

Any help would be much appreciated. Thanks.

const apiToken = <TOKEN HERE>;
const options = {
'method': 'GET',
'headers': {
'authorization': 'Bearer ' + apiToken,
'content-type': 'application/json',
'accept': 'application/json'
},
'timeout': 5000
};
let outputfromcanvas;
fetch('https://canvas.wisc.edu/api/v1/courses/123218, options)
.then(res=>{outputfromcanvas = res; console.log(res)})
.catch(err=>console.log("err: ", err))
0 Kudos
1 Solution

Accepted Solutions
James
Community Champion

 @andrews2  

Make sure you are inside the tab that is has Canvas open when you are making the request. The browser sends extra headers to prevent cross-origin requests. For example, if you had a web-page open in one browser tab and JavaScript on that page could access information in another tab from a different origin, then a malicious program could steal your banking information or make requests as you that you didn't know about.

There is a protocol set up to allow communication from other sites, it's called Cross-Origin Resource Sharing (CORS). Canvas would have to add headers to say trust the other website that you're coming from and they're not going to do that because it is a security issue.

Putting API tokens into JavaScript and running it within a browser isn't secure either. The API requests with an authorization token are not meant to be made from within the browser. Inside the browser, it uses cookies and the Cross-Site Request Forgery (CSRF) token to authenticate instead of the API token.

One thing you could do for testing is to set up a simple proxy that took your request for Canvas and made it for you. For example, if you have control of the test.example.com server, you could set up a proxy on that server. In your application, you would call test.example.com/proxy (for example) instead of canvas.wisc.edu. It would take the request, add the Authorization header, send it off to Canvas that would now accept it since it wasn't in a browser, and then return the information to your application.

Although this is not the reason for the problem, sending content-type with a GET request is discouraged. My REST client now warns "Headers are not valid. The GET request should not contain content-* headers. It may cause the server to behave unexpectedly." They do not provide a reference for that, but the Content-Type page at the Mozilla Developer's Netowrk says "In requests, (such as POST or PUT), the client tells the server what type of data is actually sent." You're not sending any type of data beyond the query string with a GET.

View solution in original post

10 Replies
James
Community Champion

 @andrews2  

Make sure you are inside the tab that is has Canvas open when you are making the request. The browser sends extra headers to prevent cross-origin requests. For example, if you had a web-page open in one browser tab and JavaScript on that page could access information in another tab from a different origin, then a malicious program could steal your banking information or make requests as you that you didn't know about.

There is a protocol set up to allow communication from other sites, it's called Cross-Origin Resource Sharing (CORS). Canvas would have to add headers to say trust the other website that you're coming from and they're not going to do that because it is a security issue.

Putting API tokens into JavaScript and running it within a browser isn't secure either. The API requests with an authorization token are not meant to be made from within the browser. Inside the browser, it uses cookies and the Cross-Site Request Forgery (CSRF) token to authenticate instead of the API token.

One thing you could do for testing is to set up a simple proxy that took your request for Canvas and made it for you. For example, if you have control of the test.example.com server, you could set up a proxy on that server. In your application, you would call test.example.com/proxy (for example) instead of canvas.wisc.edu. It would take the request, add the Authorization header, send it off to Canvas that would now accept it since it wasn't in a browser, and then return the information to your application.

Although this is not the reason for the problem, sending content-type with a GET request is discouraged. My REST client now warns "Headers are not valid. The GET request should not contain content-* headers. It may cause the server to behave unexpectedly." They do not provide a reference for that, but the Content-Type page at the Mozilla Developer's Netowrk says "In requests, (such as POST or PUT), the client tells the server what type of data is actually sent." You're not sending any type of data beyond the query string with a GET.

View solution in original post

andrews2
Community Member

Thanks! Of course you're right. I bumped the same exact code over to the node server instead of the client and it works.

Is that where the interaction with canvas will happen once I have the OAuth set up? Will I get the token through the OAuth flow and then just use that token in the place of my current one?

Thanks again.

James
Community Champion

 @andrews2  

That is my understanding. I haven't actually used OAuth yet because I haven't developed any multi-user LTI tools that need to contact Canvas on behalf of the user.

Store the original token from Canvas if possible because it will need refreshed and you don't want to make users re-agree everytime they go into your system. But you will be using the OAuth token so that you can make calls as that user.

All of the API back and forth with Canvas happens on the server because a web-client cannot talk directly to Canvas unless you're logged into Canvas and in that window/tab.

matthew_buckett
Community Contributor

The proxy handling OAuth tokens and CORS works reasonably well and we've used it to develop a couple of tools. Combined with LTI 1.3 it means you have the option of a completely static frontend along with a stateless server side component. We are using this to allow people who can't manage sub-accounts to at least browse them:

Screencast of View Sub-accounts

Regarding the use of the Content-Type header, if you set a content type that isn't one of these then the browser will issue a preflight request which reduces the performance of the application:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

Thanks for the additional information,  @matthew_buckett . I think there might be a couple of my early scripts where I was adding content-type because I didn't fully understand things and this helps clarify the reasoning. 

0a87sdf8asdf7
Community Participant

Hello, I have some experience with creating my own servers and performing API requests, but I need more clarification because I am new to CORS and getting the cross-origin error.

The first thing I did not understand was why I could perform API queries successfully using CURL, but not in Javascript (for debug only; it will eventually be moved to the server side). I read that CURL does not care about the headers, but the browser does. The only valid solution I have seen is if Canvas updated the header to allow it,

If cross-origin is an issue, why is there not some security feature to prevent CURL from making the requests?

Also, I have seen tutorials of people creating their own servers and performing API requests to modify data on the canvas side. What am I missing?

https://www.youtube.com/watch?v=LBQp0ls7mcI

https://www.youtube.com/watch?v=c8L-psDDpYE

There is also the service called GoSignMeUp, which is a "certified partner" that can create accounts and enroll users, but it is done from their server. How? Did their website get whitelisted?

Jeffrey Anderson of Mesa Community College - Maricopa Community CollegesEver wondered what someone with a little programming knowledge and patience can do wi...
matthew_buckett
Community Contributor

@0a87sdf8asdf7 Instructure could add CORS headers to their API to allow cross origin requests but as far I I'm aware they haven't allowed this to be configured on their hosted deployments.

We use a proxy that manages the OAuth 2 tokens and sets the appropriate CORS headers on responses back to clients, but without a piece of serverside code either handling the proxying or making specific requests (as in the Youtube videos) I don't think there is a way to create a request to the Canvas API from the browser.

If you are going to move the requests to a serverside component then I don't think you need to worry about CORS, unless your wanting to host the HTML that will cause the browser to make these requests from a different domain.

 

Thanks @matthew_buckett , and I have another question. I implemented oauth for a user, such as a student, but they will not have permissions to perform actions, such as listing all of the courses created in an organization and enrolling - that will be performed by the server. If I want to give my server that functionality, do I need to generate an access token from my admin account and use that in the server? Or does that violate the oauth workflow in the terms of service? And there is also a request limit for access tokens...

Update - I just discovered the Search category, which allows me to list all of the public courses; but I still only want the server to be able to enroll a user in a course.

matthew_buckett
Community Contributor

If you want to perform some action that the user doesn't have permission for then you will need to have a serverside component that has a manually generated token and use that to perform the actions (eg enrolling a user).

Locally we have a tool for managing courses that takes a mixed approach of using OAuth for 90% of operations, but then for creating a course (which our users don't have permission for) it uses a admin token that does have permission. But this approach does increase the complexity and for simple tools I'd not recommend it.

The advantages of OAuth 2 generated tokens are:

  • Better audit trail - the operations in Canvas are done as the actual user to you have a good audit trail of what's happening without having to rely on logs in the tool.
  • Good security checks - Canvas does all its security checks so you don't have to perform a second set of checks for operations.
  • Scoped Keys - By using a scoped key you can further restrict the the operations the tokens are valid for the reduces the risk that the tokens could be abused or accidentally mis-used.
  • Rate Limiting - User performing actions has their own rate limit, if all the users of your tool are using the same admin token the rate limit will be hit much sooner than if they each have their own token.

A halfway house can be using the Masquerading feature of the API (https://canvas.instructure.com/doc/api/file.masquerading.html) this allows you to not worry about the complexity but still have Canvas do permission checked (where needed).

In general the OAuth 2 flow is useful when:

  • You have a high level of usage and API limits will be a problem.
  • It's a third party supplier's tool and you don't want to give them access to everything in your Canvas installation (scoped developer keys are what helps here).