Interferon Posted February 10, 2021 Posted February 10, 2021 (edited) I am implementing the IPB REST API into a C++ program that runs on the customer's computer. This program runs without the use of a web browser. This is meant to provide a mechanism to check software licenses. In the future I also want to add these features: Allow users to upload screenshots directly from our application to the community gallery. Allow users to browse, purchase, and download IP.Downloads files directly in our application. Basically I am using IPB to build some of the functionality of Steam, for our own applications. First step: I am creating an OATH client. None of the client types describe what I am doing, so I am choosing "Custom Confidential OATH client". Here is the description: Quote Custom Confidential OAuth Client A server-side app such as a website where the code will be written in a server-side language and stored on a server that no end-user has access to. A client secret will be issued. The secret code will be stored in the program's compiled source code, so I think this is what best fits my situation. (There are potential absolutely certain DLL security issues I am not going to go into here.) For "Available Grant Types" I am choosing "Resource Owner Password Credentials" as this seems to describe exactly what I am trying to do: Quote The end-user will enter their username or email address and password which you will exchange for an Access Token. I don't know what "Redirection URLs" means so I am just adding my community URL. For "Authorization Prompt" I am choosing the default setting. Now I am confused because I am not using a web browser at all for this application. Edited February 10, 2021 by Interferon
Interferon Posted February 10, 2021 Author Posted February 10, 2021 (edited) Now let's try to connect our application. First we initialize curl: curl_global_init(CURL_GLOBAL_DEFAULT); Create a curl object: curl = curl_easy_init(); Set the URL for the request: curl_easy_setopt(curl, CURLOPT_URL, "https://www.xxxxxxxxxxxx.com/oauth/token/?key=" + publickey); Set the timeout value: curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10000); Construct the authorization header: //Construct authentication token std::string postdata; char authHeader[1024]; unsigned char buff[32]; hmac((unsigned char*)privatekey.c_str(), privatekey.length(), (unsigned char*)postdata.c_str(), postdata.length(), buff); String encoding = base64_encode((const unsigned char*)buff, 32); int len = sprintf(authHeader, "Authorization: %s", encoding.c_str()); //Construct header struct curl_slist* headers = curl_slist_append(headers, "Content-Type: text/xml"); headers = curl_slist_append(headers, authHeader); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); Here is the hmac function, which is using OpenSSL: void hmac(unsigned char* key, int keylen, unsigned char* message, int messagelen, unsigned char* out) { HMAC_CTX* ctx = HMAC_CTX_new(); unsigned int len = 32; // Using sha1 hash engine here. // You may use other hash engines. e.g EVP_md5(), EVP_sha224, EVP_sha512, etc HMAC_Init_ex(ctx, key, keylen, EVP_sha256(), NULL); HMAC_Update(ctx, message, messagelen); HMAC_Final(ctx, out, &len); HMAC_CTX_free(ctx); } And finally perform the request: CURLcode res = curl_easy_perform(curl); When I do this, the result is CURL_OK but there is an "invalid_request" error in the returned JSON object. The error description says "request must be a POST request". Okay, so my postdata value is just an empty string. Is there some kind of "hello" message I should set this to? I am just trying to break through the network at this point and make sure I can actually communicate with the server. Edited February 10, 2021 by Interferon
Interferon Posted February 10, 2021 Author Posted February 10, 2021 (edited) Following example here: https://invisioncommunity.com/developers/rest-api/index/ I changed the URL to "https://www.xxxxxxxxxxx.com/api/core/hello/" but it is saying the key is invalid: curl_easy_setopt(curl, CURLOPT_USERPWD, publickey.c_str()); If I use an API key then it actually works, but I want to use the per-user OATH method since this is running on the user's computer. Edited February 10, 2021 by Interferon
Interferon Posted February 10, 2021 Author Posted February 10, 2021 Using PHP and an API key I can very easily view purchase status given a user ID and an item ID. Creating our own intermediate web API might be a good idea, because it would prevent mountains of data from going back to the user. Should I be looking at an approach like that instead?
bfarber Posted February 10, 2021 Posted February 10, 2021 You will probably want to create a "Custom public OAuth client" which has the description Quote A browser-based app written in JavaScript or a mobile/native app which will be installed on a device an end-user does have access to. No client secret will be issued. Note the "native app which will be installed on a device an end-user does have access to" bit. From there, I agree you will want to use "Resource Owner Password Credentials" as the grant type. "Redirection URI" won't mean much in your case so you can set it to whatever you want. Here's a generic description of how it works: https://auth0.com/docs/flows/resource-owner-password-flow You'll send the details to the /oauth/token/ endpoint as a POST request with client_id: The client ID for the OAuth client username: The user's username password: The user's password scope: Space separated list of scopes to grant access to grant_type: "password" This should return the access token, which you can then use to make REST API calls authenticated as the end user.
CoffeeCake Posted February 10, 2021 Posted February 10, 2021 5 hours ago, Interferon said: The secret code will be stored in the program's compiled source code, so I think this is what best fits my situation. (There are potential absolutely certain DLL security issues I am not going to go into here.)
Interferon Posted February 10, 2021 Author Posted February 10, 2021 1 minute ago, Paul E. said: How else would you possibly do it?
CoffeeCake Posted February 10, 2021 Posted February 10, 2021 You'd want to rely on the username/password supplied by the end user. A custom public oauth client, not a custom confidential oauth client. Interferon 1
Interferon Posted February 10, 2021 Author Posted February 10, 2021 (edited) I will investigate the OATH stuff now. I was able to make a simplified web API very easily that checks ownership and active status of a license using PHP. Edited February 10, 2021 by Interferon
Interferon Posted February 10, 2021 Author Posted February 10, 2021 (edited) .... Edited February 10, 2021 by Interferon
Interferon Posted February 11, 2021 Author Posted February 11, 2021 (edited) We have the authentication token working now. I can see the authentications in the IPB admin panel, along with the correct scopes. However, every API call we try to make results in a NO_PERMISSION error. The URL is set like this: std::string url = "https://www.xxxxxx.com/forums/api" + endpoint; Where endpoint is equal to "/core/me". The token appears to be working, because if I set it to a random string I get an error saying it is invalid. The URL appears to be correct because if I make up an endpoint that doesn't exist I get an INVALID_CONTROLLER error. The error code is 2S291/3. I also notice I don't see any of my calls using an OAUTH token as the credential appearing in the API log. Edited February 11, 2021 by Interferon
bfarber Posted February 11, 2021 Posted February 11, 2021 Well, unlike most areas with a unique error code I can see this error code is repeated a few times in our code base (an issue I'll address shortly). For the combination of this error code and the response NO_PERMISSION, it would indicate that either (1) you are using an OAuth access token but hitting an endpoint tagged as "apiclientonly" (meaning only an API key can access the endpoint), or (2) you are not using an OAuth access token and are hitting an endpoint tagged as "apimemberonly" (meaning an API key cannot be used to hit this endpoint). The /core/me endpoint is tagged as "apimemberonly". This makes me think you are making your request using an API key and not using the OAuth access token. Take a look at this page: https://invisioncommunity.com/developers/rest-api . If using an OAuth access token, you should be setting an authorization header like so: Authorization: Bearer {$accessToken} $accessToken in this case is the access token that was returned.
Interferon Posted February 11, 2021 Author Posted February 11, 2021 Yes, that is exactly what we are doing. I sent a PM.
Interferon Posted February 12, 2021 Author Posted February 12, 2021 Maybe there is a publicly accessible application we can hook into for testing? That could eliminate any server-side errors on our site.
Recommended Posts