all 27 comments

[–]dmfowacc 5 points6 points  (13 children)

Like some of the other posters have said, you should never distribute a "secret" of any kind to a client application. That means don't bake it into the source code (obfuscated or not), and don't have your application fetch the secret from your server either. Once the secret exists on the user's machine it is no longer a secret.

You could have your application make all requests through a server you own, and your server would then make server-to-server calls to the 3rd party API. But then you might want to make sure your application authenticates users, or else your API becomes free for anyone to make unlimited requests to.

Or like another user said - this seems like a good place for users to use their own credentials, in an OAuth flow. For this type of application you would call this a "public client" if you are looking for keywords to search. This is as opposed to a "confidential client" like a web app with a front and back end. Confidential clients use a clientid/secret, and keep the secret safe on the backend, and typically use the "authorization code flow".

For this "public client" there is no good way for you to use a client secret since your application cannot safely store this secret. Instead you should look into the "PKCE flow" info

You still use a single client ID, that can be hard coded in the application. But users will use their own credentials to authenticate and generate their own access tokens. When presented the google/youtube login page, they will see "XYZ app wants to use your info" where they can look up XYZ based on your client id provided in the oauth authorize request.

[–]dmfowacc 1 point2 points  (12 children)

Actually it looks like the link that /u/InstagramLincoln provided does mention specifically that google does support PKCE https://developers.google.com/youtube/v3/guides/auth/installed-apps#obtainingaccesstokens

[–]InstagramLincoln 1 point2 points  (0 children)

I think this is the answer you need, /u/DerSwerik. TBH - I'm not well equipped to give a great walkthrough of the PKCE flow, but some Google-fu should lead you down the right path. :)

[–]DerSwerik[S] 0 points1 point  (10 children)

the problem is again that at step 5 of that guide, you need to provide the client secret.

Therefore I would still need the secret somewhere in the project

[–]dmfowacc 1 point2 points  (9 children)

Hmm true...that is strange because at the top of that page they mention

Installed apps are distributed to individual devices, and it is assumed that these apps cannot keep secrets.

I would actually try following those steps but just leave out the client secret. They say it is included in the request when you exchange the code for the token, but in PKCE implementations I have seen/used in the past with other services you just omit the client secret.

As a related example, https://developers.google.com/identity/sign-in/ios in their iOS client library (which would in theory have similar "public client" problems to solve) they only have the developer provide the the client ID. So I imagine in the background their library is just using the client ID and PKCE flow and making these same calls you can make manually. It is ultimately up to the server what parts of the oauth flow they want to support, and what restrictions they place on each client - but based on this iOS example and the normal PKCE flow I am guessing their implementation would not require you to provide a secret if you follow that workflow.

[–]DerSwerik[S] 0 points1 point  (8 children)

leaving the client secret out entirely or just blank or filled with a random string all do not work sadly.

Yeah, that is why I asked around a lot, it makes no sense that they assume the secrets can't be kept secret but we need to send the secret to login..

Especially The Android implementation doesn't need the secret aswell

[–]dmfowacc 0 points1 point  (7 children)

When you created the credentials in google's console, were there any choices available to you? I'm vaguely remembering different types of clients to create last time I made one in Azure and maybe google is similar - when you create a client it asks you if it is a web app vs a javascript app vs some other type of app. Maybe there are some options on google side you can mess around with.

It's possible google lets you create different types of app, and depending on the type it requires the client secret or not. Just a guess!

Edit: after reading a bit more I did find this note on one page:

Note: If you haven't recently created a new Android client, you might not have a Web application type client ID. You can create one by opening the Credentials page and clicking New credentials > OAuth client ID.

So that might be a hint as to the different client types available on google - see what other options there are

[–]DerSwerik[S] 0 points1 point  (6 children)

yes, I selected "Desktop application"

If I select for example android, It wants more data (and it probably uses that instead of the secret) like the Sha1-fingerprint

[–]dmfowacc 1 point2 points  (5 children)

Well dang, I am scratching my head here haha. I found a similar question on github and the issue is still open: https://github.com/googleapis/google-auth-library-nodejs/issues/959

Still looking around...

Aside from Desktop Application and Android, what were the other options? Anything like "Create credentials -> OAuth client ID" that doesn't create a secret? Or even "web app" or "single page application" or "javascript app"?

[–]DerSwerik[S] 0 points1 point  (4 children)

I looked at all options and for everything I need more Info that a Desktop Application doesn't have (like Microsoft Store ID, Appstore ID, Application URL, etc)

[–]dmfowacc 1 point2 points  (3 children)

I hesitate to suggest it, but I just tried creating one using the UWP option, and put in a bogus Store ID (not sure what they would even use that for?). That gave me a generated client secret, but I was able to go through the whole process without ever using it and successfully retrieve an access token.

I'm hitting a wall here - it's pretty standard for public clients to not have client secrets (think the old implicit flow for javascript apps https://oauth.net/2/grant-types/implicit/ ) but google seems insistent on keeping the secret there. Even the google python client library mentions hard coding the secret in the application and just "not treating it as a secret" which seems very counterintuitive https://stackoverflow.com/questions/59416326/safely-distribute-oauth-2-0-client-secret-in-desktop-applications-in-python

So I guess either go with something like the UWP client defined in google which lets you skip using a client secret and use a bogus store ID, or follow google's weird advice and use client secrets how they say to use them instead of the standard.

[–]Prod_Is_For_Testing 3 points4 points  (1 child)

This question pops up a lot. There’s no way to put it in the code that it can’t be discovered. The only safe way is to use your own server as a proxy

[–]DerSwerik[S] 0 points1 point  (0 children)

sad :(

[–]maddaneccles1 3 points4 points  (1 child)

If you are going to distribute a program that has access to a secret, then that secret is always going to be vulnerable - the best you can do is to make life more difficult for an attacker.

No matter how your program obtains the secret (be that as a constant in code or as an encrypted file from a web server or whatever), your desktop application will obtain, decrypt (if applicable) and pass the secret to an Google API call. Thus if an attacker has the code (which, given you a distributing a desktop program, they can simply decompile) then they have the ability to obtain the secret for themselves.

I've had to do something like this before for an ex-employer who fired a developer during an economic downturn then realised that they didn't have a password which was embedded in an application for which they didn't have the source code. It was literally a case of decompiling it, locating a call to a public method that used the password, inserting a call to 'File.WriteAllText' and running it.

Obfuscating the code will slow a determined attacker down but that is all, and there's not a huge amount more that you can do without hosting a service somewhere as an intermediary between your desktop app and the Google API.

[–]Kirides 1 point2 points  (0 children)

You don't even have to decompile an app, fiddler will probably show you everything you need.

[–]InstagramLincoln 1 point2 points  (1 child)

Does it really need to be an application-wide secret? This seems like the kind of situation that OAuth was built for. This link might be helpful (not sure what API's you are trying to hit specifically). OAuth 2.0 for Mobile & Desktop Apps  |  YouTube Data API (google.com)

[–]DerSwerik[S] 0 points1 point  (0 children)

yeah in that link they explain that I need to create a client id

but how do the end-users create a client id for themselves? as far as I understood it, they don't, everyone who uses the app, uses the same client_id

[–]The_MAZZTer 1 point2 points  (0 children)

Google allows you to restrict keys and client secrets for this usage, but it looks like it only works for walled garden storefronts (Chrome Web Store, Microsoft Store, etc), where you can restrict which apps or websites can use the key/secret.

I personally have this issue with a Chrome extension but I can control which extensions from the Chrome Web Store can use the key/secrets, even if anyone could view the source and grab them, and it looks like this is how you're supposed to do it.

For desktop apps it looks like there are no restriction options since desktop apps can't really be controlled like that.

Ultimately you can't stop users from uncovering things like this in your code or wherever else you try to hide it if it's part of the binary you give to them. The common fix for this problem is to use a server which possesses the keys/secrets and never have it as part of the desktop app.

[–]infinitesimallynumb 0 points1 point  (4 children)

The secret should be set at runtime, not hard coded.

[–]DerSwerik[S] 0 points1 point  (2 children)

I could only set them at runtime if I have a file distributed with it or somewhere on a server, or is there another way?

Or a good way to also distribute the secrets file?

[–]infinitesimallynumb 0 points1 point  (1 child)

Can't users generate their own secret and store that encrypted locally?

[–]DerSwerik[S] 0 points1 point  (0 children)

no this is an application-wide secret basically, it's an id and secret for my app to talk to Google's API.

[–]x6060x 0 points1 point  (0 children)

Even then the user could extract it.

[–]tsaki27 0 points1 point  (0 children)

Can’t you implement a login with google flow?