Why isn't clientID enough for using Google APIs? - javascript

From Google's API explorer and using Authorize requests using OAuth 2.0 as I want to on my SPA, I see a YOUR_API_KEY is somehow derived from a login and used in subsequent calls to:
https://www.googleapis.com/youtube/v3/channels?part=statistics&id=UCE5Au4LfcBHxTQR_yLbncrQ&key={YOUR_API_KEY}
How do I get this key? I am working from GAPI.auth2.getAuthInstance() in a VueJS app.
I can see gapi being used in Google samples. But I just want to do simple direct fetch calls with this user's API key for the session. I do not want to use gapi.client, or do I have no choice?
Update: On the Oauth2 playground I see it refers to a client secret 1. I don't understand this at all, since with a SPA with no backend, you don't want to store a secret! Why isn't the client ID and the origin enough?
Here's another confusing example (no secret used) where the apiKey is used together with the clientId. Why isn't clientId enough, since it's restricted by origin?!

Generally, there are 2 types of OAuth flows to obtain an access_token when on the web. One is called implicit flow and other is called authorization code flow.
For the code flow, you would require the client_secret in order to exchange a code with an access_token. This usually happens on server side.
For the implicit flow, you can simply give a client_id to obtain an access_token and it is designed to work on client side.
The screenshot you've attached is the OAuth flow part where you exchange an auth code with an access_token. Because the playground is using the authorization code flow.
What you are looking for should be the implicit flow.
To achieve this in YouTube client library (or whatever Google javascript client lib), you don't need a client_secret. You can find a complete example using Google Drive here:
https://github.com/GoogleChrome/google-sign-in/blob/master/static/scripts/authorization_client.js

Related

AWS Cognito - AdminInitiateAuth vs InitiateAuth

We're looking to leverage AWS Cognito for authentication with an architecture that looks like:
client (browser) -> our server -> AWS Cognito
With various configurations set, initiateAuth seems no different to AdminInitiateAuth and so I'd like to understand when under these configurations if it matters whether one is chosen over the other.
It seems that when I create an app with a client secret and use initiateAuth, it seems to be almost the same integration experience as adminInitiateAuth that uses the ADMIN_NO_SRP_AUTH auth flow. The latter does not even require AWS credentials as stated in the AWS documentation. My integration with Cognito is as below:
initiateAuth:
const payload = {
AuthFlow: "USER_PASSWORD_AUTH",
ClientId: cognitoClientId,
AuthParameters: {
USERNAME: username,
PASSWORD: password,
SECRET_HASH: generateSignature(username)
}
}
const response = await cognitoClient.initiateAuth(payload).promise();
adminInitiateAuth:
const payload = {
UserPoolId: userPoolId,
AuthFlow: "ADMIN_NO_SRP_AUTH",
ClientId: cognitoClientId,
AuthParameters: {
USERNAME: username,
PASSWORD: password,
SECRET_HASH: generateSignature(username)
}
}
const response = await cognitoClient.adminInitiateAuth(payload).promise();
You can see the difference is the different AuthFlow values, calling different methods and ADMIN_NO_SRP_AUTH requiring the UserPoolId parameter which seems superficial to me.
We are also generating the signature based on the client secret which is something that we would handle securely.
I understand that you would like to know the difference between the InitiateAuth and the AdminInitiateAuth API calls in Amazon Cognito.
To clarify the usage of the API calls:
InitiateAuth is a client/browser side API call, and the API call does not need any sensitive credentials to give a challenge and other parameters.
AdminInitiateAuth is a meant to be run in the server side, and the API call always needs developer credentials to give a successful response. This is because the API call is an AWS SigV4 signed API call.
Furthermore, both the API calls support different Auth Flows as specified below.
InitiateAuth supports the following Auth Flows:
USER_SRP_AUTH
REFRESH_TOKEN_AUTH
USER_PASSWORD_AUTH
CUSTOM_AUTH
Kindly note that the AWS CLI documentation [a] currently states that ADMIN_NO_SRP_AUTH is a possible value. However, I have tested the API call on my end and I can confirm that the documentation for the CLI is currently incorrect.
UPDATE (12/09/2019): It looks like after this answer was written, Amazon Web Services has updated their documentation to the correct possible values. The documentation now states the following:
ADMIN_NO_SRP_AUTH is not a valid value.
AdminInitiateAuth supports the following Auth flows:
USER_SRP_AUTH
REFRESH_TOKEN_AUTH
CUSTOM_AUTH
ADMIN_NO_SRP_AUTH
USER_PASSWORD_AUTH
Example use-case of InitiateAuth: If you want your users to authenticate into your web application.
Example use-case of AdminInitiateAuth: Any use-case that needs server side authentication or access based on specific AWS Credentials to filter that only specific IAM users can authenticate using Cognito.
As stated by george earlier, InitiateAuth would be ideal for your use-case as your application is a client side application.
Additionally, if you are concerned about security, you could use the USER_SRP_AUTH with InitiateAuth. For more information about using the USER_SRP_AUTH flow in your production code, you could refer to the following NPM documentation[b].
References
[a]. https://docs.aws.amazon.com/cli/latest/reference/cognito-idp/initiate-auth.html
[b]. https://www.npmjs.com/package/cognito-srp
initiateAuth and adminInitiateAuth do a similar thing, however, they have different use cases and flow.
initiateAuth is used when you have an end user client app. The user enters their creds and they are sent via Secure Remote Password Protocol. If the flow succeeds the end user gets a token back and is allowed access. This flow is used by the Android, IOS and Javascript SDKs because it's to do with the client side.
adminInitiateAuth is used when you don't have a client end user app but a secure back-end app using Java, Python or some other backend language. This method does not accept username-and-password user credentials for admin sign-in but requires AWS credentials.
In your case, if you had a client app ---> Cognito and use for example Android SDK or Javascript SDK directly then you should use initiateAuth from within the SDK passing the user credentials. However, browser -->back-end--> Cognito meaning you have a dedicated back-end so in your case you should adminInitiateAuth. More info here
AdminInitiateAuth only exists for one reason: so you can avoid the hassle of SRP in your server side code while still requiring client side code to use SRP.
SRP is more secure, but annoying/inconvenient to implement. Also, if you're writing code that runs on the server, a lot of the benefits that SRP provides are irrelevant anyway (your code is running in a secure, protected environment).
If you set up a Cognito app client like this:
[X] ALLOW_USER_SRP_AUTH
[ ] ALLOW_USER_PASSWORD_AUTH
[X] ALLOW_ADMIN_USER_PASSWORD_AUTH
... then any untrusted/public client side code must use SRP, but trusted server side code is free to use the plain old user/password flow. (Of course the server side code has to have AWS credentials in order to enjoy this privilege)
You're absolutely spot on though that the two are pretty much equivalent in functionality with some superficial differences.
I too spent quite some time researching scarce documentation on the topic of
when to use AdminInitiateAuth vs InitiateAuth.
https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html is supposed to help, but I find it poorly structured and very confusing.
From my understanding, you're right, you can use both approaches on the server:
InitiateAuth with AuthFlow=USER_PASSWORD_AUTH (requires app client to be created with client secret).
AdminInitiateAuth with AuthFlow=ADMIN_USER_PASSWORD_AUTH (replaced legacy ADMIN_NO_SRP_AUTH)
I believe second option makes more sense for the server usage scenario though. This way you can disable ALLOW_USER_PASSWORD_AUTH auth flow in the app client settings altogether. While probably not a huge risk, it feels cleaner to not have InitiateAuth API open to the public since it's not required.

How to secure the JavaScript API Access Token?

There are numerous online resources which provide JavaScript APIs to access their services. To be more clear, I will base my question on the example of MapBox, but this applies well to many other services in various domains.
When someone wants to use such a service in a web application (like the map imagery from MapBox for example), they typically need to Register/Sign Up and obtain an access token to access the service.
Now, if I would use the API from the server side - there is no issue: I know my token is stored securely somewhere on the server and is only exposed upon communication between my server and the service provider, which is OK as long it is HTTPS. However, in case of a JavaScript API (for example if I use Leaflet to render a map from MapBox), I am supposed to have my access token in a JavaScript which is exposed to the user's web browser - and so it makes it extremely easy to find someone's access token. My users, or in a case of a public service, literally anyone, would be able to find the token in the browser's "Dev Tools".
This token however, as for me, should be considered as a sensetive data - service usage is tracked based on the authentication this token provides. If you pay for the service based on its usage it becomes critical, but even if you don't (like, if you use a Free/Starter/Non Paid plan) - service usage is limited and I'd like to be sure it is only me who uses it.
Is my only option a proxy via my own web server?
Is there a way to secure the access token used by a JavaScript API to access an external service, provided that JavaScript is executed in a user's browser?
Restrict Access with CORS
Make your web server return the access tokens on an ajax request from you javascript with CORS setup. Token can be captured with this method visiting your app.
Provide Tokens to Authorized Users
You can also add authentication on your webserver to provide limited access to the users you allow. Token can be captured with this method but only by authorized users.
Proxy Requests
The only way to completely protect that token is to proxy the requests through your server. Token cannot be captured with this method. Note that this may be against terms of service.
Javascript API tokens (and all client tokens, in fact) are always visible to the client (unless using them only server-side, as in node). There is no way around that. As you mentioned, the only way to truly secure an API key and keep it private is to store it in the server, then request the server to make the request on the client's behalf.
5 years later, this is not necessarily for the original poster but for anyone still interested, Mapbox now allows you to easily restrict tokens by domain(s):
https://account.mapbox.com/access-tokens (assuming you are signed in)
I will speak only about map imagery APIs like Mapbox, it seems that unfortunatly only services like Google Maps, Here Maps, Bing Maps etc offer ip/domain filtering by service provider or this type of security, all offers based on OSM i met don't propose it. As Justin Poehnelt said the only reliable way is to build a proxy, but it's usually forbidden. I find this in the ToU of Mapbox:
You may not redistribute Map Assets, including from a cache, by
proxying, or by using a screenshot or other static image instead of
accessing Map Assets through the Mapping APIs.
You may like to read up on CORS headers
These allow you restrict which domain can call a remote web service.

authentication using Doorkeeper from client-side application without transmitting secrets

I'm trying to use Doorkeeper in a Ruby on Rails API app for authentication. From a client-side AngularJS app, I want to get an access token. Currently, this involves a GET request to /oauth/authorize, which gives me a code, then I POST that code along with a client_id and a secret to /oauth/token. I don't want to have to send the client ID and secret from my client-side app, since they're stored in plain-text in a JavaScript file. I would like to follow this flow where response_type is token, not code, but I can't figure out how to do that with Doorkeeper. Their wiki examples all seem to involve POSTing the client ID and secret to /oauth/token.
Is Doorkeeper the right gem for this? How can I do Google OAuth2 from a client-side app, where no secrets are passed from client-side to the server?
Edit: looks like what I want is Implicit Grant, which Doorkeeper supports. Now I just have to find out how to do that in my Rails app...
No extra server-side configuration necessary.
When I created a new Doorkeeper::Application in my Rails app, the Doorkeeper interface gave me an Authorize link with response_type=code in it for that application.
I changed that to response_type=token and when I do a GET request to that, it responds immediately with access_token instead of code. The Authorize URL looks like http://my-rails-doorkeeper-app/oauth/authorize?client_id=1234&redirect_uri=http://my-angularjs-app&response_type=token.
Update :
This can only be applied when we also allow implicit grant for the grant flow.
By default, doorkeeper will allow its four kinds of flow (implicit grant, authorization grant, password, and client_credentials).
You can configure it in initializer/doorkeeper.rb if you don't want to let it happens since sometimes it can be dangerous.

Signing webservices api calls with javascript

I'm looking for a nice pattern that woud help me to fully sign my api calls with javascript (here for some example, vimeo) after some oauth connect retrieved authorization identifiers.
Using ruby with omniauth, what I'm looking for would be to retrieve the url that gets called when you do a ModelName.{generateTokenMethod}.request(:get,{url})
It is possible. There are a handful of oauth 1.0a libraries for javascript (You could try looking at some node.js code as an example).
The problem with using oauth in client-side javascript is that it will expose your client secret to anyone using your web service.
Anyone who has your client secret can make requests on behalf of your application, and lure users into generating access tokens by masquerading as your application.

Server-side flow for Google Drive API authorization of a javascript Chrome extension

I was reading #Nivco answer to Authorization of Google Drive using JavaScript and saw:
"...all you have to do it is use server-side code to process the authorization code returned after the Drive server-side flow (you need to exchange it for an access token and a refresh token). That way, only on the first flow will the user be prompted for authorization. After the first time you exchange the authorization code, the auth page will be bypassed automatically.
Server side samples to do this is available in our documentation."
Having read the documentation I am still pretty confused about how to process the authorization code and ultimately pass the access and refresh tokens to my Chrome extension so that it can proceed without the server for future requests. Can someone provide an example of the server-side code to do this?
As background I have a Chrome Extension with several thousand users that is built on the Google DocList API but I am trying to transition to the Drive API since the other one is being deprecated. Ideally my code would be entirely stand alone as an extension but I'm willing to accept the single authorization request through my server that Nivco's answer requires.
Thanks!
We've just ported our JavaScript application from using server to client flow. We've removed the server part entirely, it's not needed any longer.
You can see the source code that we used online, it's available uncompressed.

Categories

Resources