User API keys specification

Thanks! Yes, “regular API tokens” for users is what i was after. I’m sure I’d seen that a million times, and never realized it was there. :heavy_check_mark:

3 Likes

Hey everyone I’m not sure if this was resolved? I see that it’s possible to create an “All Users” and (per user) level API token as an admin, but I’m interested in giving a user the power to generate his/her own token. Has there been work done on this? It sounds like the original thread was getting at this idea, and then it was lost. Thanks!

Download the mobile App and then add a site, that uses the user api key system you have to follow a very strict workflow, no plans to expose arbitrary generation of keys in user prefs

3 Likes

Thank you! I downloaded the app, and then realized that the sites I belong to don’t have this enabled (I’ve been testing on my local machine with the Dockerized discourse). Just to make sure we are talking about the same thing - I’m interested in generating tokens to use just a scoped subset of functions (and not global API keys to do all the things). Is this what you are talking about?

It seems like, given that there is an endpoint to generate tokens, it would be logical to provide this function (to users with a certain trust level, for example) from within the site. Otherwise, it would need to be the case that the site generates some external page (with a server to hide an admin token) to generate the keys for the user. Is it the case that 1. there is no internal generation of tokens for the user (and why not?) and 2. Nobody has created some external app (javascript, flask, anything really) to perform this action?

1 Like

Yes.

Possibly, conceptually

  1. This is tricky to consume cause you need to use custom HTTP headers (by design)

  2. It would be very hard to educate even TL3 users about what the point is of this thing.

I prefer not to ship UI for this in core, but maybe you should write a plugin for your specific use case?

Can you explain a bit more about the “why” here?

The design of the system centers around “I have an external dedicated program”, “this program knows how to follow Discourse protocol”, “It asks for token”

API keys (non user ones) are much easier to consume cause you just append them after the ?

3 Likes

Can I pass the API key generated from the user page on this? I was trying to use that API key and seeing ‘You are not permitted to view the requested resource.’ error on /categories.json API request

Hi guys

Apologies if this is duplication but I am having trouble digesting the steps there even though they are very well put together

I have a discourse installation on https://forum.domain.com and a site on https://development.domain.com from which I need to make calls to the discourse installation to pull and set some data.

All requests work from postman with Api-Username and Api-Key - no problem

However for cross origin requests using JS discourse doesnt allow the Api-Username leading me down the path of the flow described in this thread i.e. using the User-Api-Key and the User-Api-Client-Id

I basically require a description of paramaters PUBLIC_KEY,NONCE,CLIENTID used in the sample request below and where I can get them…

https://forum.domain.com/user-api-key/new?public_key=PUBLIC_KEY&nonce=NONCE&scopes=SCOPES&client_id=CLIENTID&application_name=DEVELOP&auth_redirect=XXX

Finally will this flow allow for seamless api requests from my https://development.domain.com to https://forum.domain.com without the need for authentication.

PS I have SSO set up between the forum and the site from which the requests are being made so the user will be logged in…

Thanks for any guidance

One sec, is this a per user thing? Are you allowing arbitrary users to do this?

Our server API supports header based auth these days so it can work with CORS just like the user api keys do. You would use the user api keys if you want to restrict scopes and allow end users to generate the keys vs admins.

2 Likes

I wouldn’t see each user requiring a separate key no… One admin key should do what I need.

I can consume the api no problem through postman. For example a GET to /notifications.json?username=alanmurphy returns the data no problem using just api-key as a header.

If I trigger that request from the console of a discourse installation I get data back no problem also i.e.

var xhr = new XMLHttpRequest();
xhr.addEventListener(“readystatechange”, function () {
if (this.readyState === 4) {
console.log(this.responseText);
}
});
xhr.open(“GET”, “https://**********.com/notifications.json?username=alanmurphy”);
xhr.setRequestHeader(“api-key”, “d06ca53322d1fbaf383a6394d6c229e56871342d2cad953a0fe26c19df7645ba”);
xhr.setRequestHeader(“api-userame”, “system”);
xhr.send();

All Good:+1:

However, if I do this from the sub-domain I am wishing to contact discourse from, it tells me that it has been blocked by CORS policy: Request header field api-key is not allowed by Access-Control-Allow-Headers.

The allowed headers are:

Content-Type, Cache-Control, X-Requested-With, X-CSRF-Token, Discourse-Visible, User-Api-Key, User-Api-Client-Id

If I could just get guidance on what auth params to pass for cross origin requests and where to get them that would be very helpful.

Ps. My discourse is set up to allow Cross Origin requests from the domain so I believe it is purely a headers issue

Thanks

I find allowed_user_api_auth_redirects default of “discourse://auth_redirect” rather restrictive, especially because “discourse” does not appear to be a valid URI scheme.

Please explain the thinking behind this default. Thank you.

I am having this issue as well. If I initiate the API from a JS application, then automatically the allowed headers are: User-Api-Key, User-Api-Client-Id even though I do not need user API keys. All I want is a simple API key but I cannot get anything to work. If I try to pass Api-Key in the headers I get a CORS error since it expects User-Api-Key. But when I try to use User-Api-Key, I get 403 errors. I am stuck. I would think this is the base usage for using the APIs. I am not trying to do anything out of the ordinary. I am simply trying to create a new topic post.

2 Likes

That is the custom URI scheme used by the DiscourseHub app for iOS and Android.

5 Likes

I’ve got a question concerning the “read tokens” and “write tokens”. This comment here is from 2016, so this possibly had already been changed? Or are the defaults still only “read tokens”?

Background: I’m one of the coders behind a distributed social media system. We already do have connectors to non-federating systems. The idea is to write an addon for discourse as well. But when most likely most system won’t allow users to generate tokens that allow posting, we will try another way. We already do have a mail connector. Then we will simply use the mailing list function of Discourse and we will try to enhance the returned content and will post via SMTP.

You can do write tokens if you ask for the scope upfront

3 Likes

Of course this is always possible. But I have the feeling that this is a support nightmare. Our software has got some hundred installations with (in total) more than 10k users. When they see that there is an addon that is connection to Discourse, many will surely like to use it. And since it most likely won’t work out of the box, this will generate questions and support work from our side. Additionally it will generate work for the admins of the several Discourse installations. And very likely not all will allow it - which will cause frustration.

So possibly at first I will focus on integrating the mailing list mode mails. Or is it possible to combine these two? Means: Reading of the posts via the API, but posting via SMTP?

Hi…I don’t know to generate public_key… should I use RSA generator to get public/private key?
If so I have done with some online RSA generators. but I am getting this error:

OpenSSL::PKey::RSAError (Neither PUB key nor PRIV key: nested asn1 error) /var/www/discourse/app/controllers/user_api_keys_controller.rb:189:in `initialize'

Also, I want to ask you guys if this suits my user case:
I have an app, and I want to basically authenticate user and get the username, is generating api key flow the simplest flow for me to validate user’s login in my app? If possible, I want avoid SSO because it seems more complicate.

Same boat here, although I am only trying to use User-Api-Key (not Api-Key) to create a topic post and am getting CSRF denial from the actionpack library.

Unless the discourse server has turned off CSRF checking, posting from a third-party desktop app seems hard. I’m not about to emulate a browser.

@sam What is your take on allowing User API Keys that only have the read scope attached, to be passed via URL params on GET requests?

Use case is allowing integrations like subscribing your Improved Bookmarks with Reminders in Google Calendar using User API keys.

5 Likes

How about creating a specific new scope, with a third parameter to indicate “get param allowed”. That way, people can’t misuse it for other things (e.g. bypassing CORS and requesting the discourse API from another site).

(from here)

SCOPES = {
    read: [:get],
    write: [:get, :post, :patch, :put, :delete],
    message_bus: [[:post, 'message_bus']],
    push: nil,
    one_time_password: nil,
    notifications: [[:post, 'message_bus'], [:get, 'notifications#index'], [:put, 'notifications#mark_read']],
    session_info: [
      [:get, 'session#current'],
      [:get, 'users#topic_tracking_state'],
      [:get, 'list#unread'],
      [:get, 'list#new'],
      [:get, 'list#latest']
    ],
+   calendar: [ [:get, 'users#bookmarks_cal', true ] ],
  }

(Aside: why are we using nested arrays here…)

10 Likes

I like that the API key would be flagged explicitly as “allowed in GET” at the user level.

As a whole the option could be open for any GETs. The rule I like is, when operating in this mode:

  1. User API key is 100% restricted to a single specific GET controller action
  2. User API key is flagged as allowed in GET query params.

This limits the impact of any leak here via a proxy cause the key will never be reused.

I guess {get: 'list#new'} , {get: 'list#latest'} would work as well.

7 Likes