authentication using Doorkeeper from client-side application without transmitting secrets - javascript

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.

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.

Why isn't clientID enough for using Google APIs?

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

Authenticating with ADAL JS not behaving as expected

I have two Azure AD applications in the same directory, let’s call them FrontendAuth and BackendAuth, which provide authentication for an ASP.NET MVC frontend and a Web API backend, respectively. The MVC frontend is protected using the standard UseOpenIdConnectAuthentication configuration, the Web API backend with UseWindowsAzureActiveDirectoryBearerAuthentication.
What I want to do is log into the frontend, authenticate against FrontendAuth, then consume via JavaScript the API hosted in the backend by providing a token, acquired using ADAL JS, to BackendAuth.
Assumptions
My expectations/assumptions are:
That I would have to configure FrontendAuth to have access to BackendAuth in the classic portal
That I would have to edit the manifest files of one or both of these to set oauth2AllowImplicitFlow to true
That when I configure ADAL JS I should be setting clientId to be that of FrontendAuth
The endpoints object of the ADAL JS configuration should contain the Url of the backend API and the client ID of BackendAuth
Outcome
I can achieve my goal of logging in to the frontend and communicating with the backend service via ADAL JS with:
The FrontendAuth application having no access to BackendAuth at all
Neither manifest file having the oauth2AllowImplicitFlow property set to true
ADAL JS having the clientId set to be that of BackendAuth
The endpoints object of the ADAL JS configuration not set at all
Questions
Based on these findings I would like to understand the following:
Were my assumptions correct? Is this how ADAL JS is intended to work?
Why did the lack of application access and unchanged manifest files have no effect on whether the authentication succeeded?
When do these measures have an effect on the authentication outcome?
You are mixing up two OAuth2 flows here (authorization code flow and implicit flow). Both are meant to issue a token to a client application. The auth code flow is used for web apps running on the server (like your MVC app) and the implicit flow is meant for public clients like a SPA.
When you use OpenID Connect to sign in your user to your MVC application, using the hybrid flow, you receive an authorization code from the browser. You use this code to talk to the authorization server and get a JWT token which is then stored in a cookie session. You can use the same code to get a JWT token for your BackendAuth app, as long as you have given permission to your FrontendAuth app to call the BackendAuth app.
If you want to enable the JavaScript in the user's browser to call into the BackendAuth app, you'll need to somehow pass the access token to the browser. You can do this by sending the token along with the initial request and put it in local storage or expose a (secured) MVC route to get the token.
For an example of what I'm describing here see this Azure AD sample, which acquires a token for the Graph API using the authorization code is received.
ADAL.js implements the implicit flow and is meant for JavaScript applications like SPAs etc.
It sounds like you haven't explicitly decorated your Web API controllers with [Authorize] attributes (either at the class level, or the action level). Thus, your Web API may be happy to serve content to anyone who requests it.

Authenticate client-side app to REST API using CORS with local strategy

The Problem:
Serving a secure API to a client side app using only a local authentication strategy. The red arrows are part of the knowledge gap.
Context:
That is --- client.example.com is making a POST to api.example.com/login where on success client.example.com can gain access to a GET service like api.example.com/secret.
An idea!
Implimentation of OAuth 2.0 with hybrid grant type sitting in front of API.
Why hybrid?
It wouldn't be an Implicit Grant Flow aka Client-Side Web Applications Flow because there is no redirection to API server too grant access token. (i.e.) "Is it ok for so-and-so to access your data?"
It wouldn't be a Resource Owner Password Flow because a Client ID and Client Secret are passed along with the request so it's assumed the client app is server-side.
OK... so what about a little bit of both?
What if we used a CRSF token on page load of client-side app, and POST it with user credentials too OAuth 2.0 authentication endpoint to exchange for access token? You would authenticate each subsequent request with the access token and CRSF token after a successful login.
A good Node.js OAuth 2.0 library I found:
https://github.com/ammmir/node-oauth2-provider
Help Me!
I can not find a working example of an authentication measure that solves this problem! Point me in the right direction?
Ultimately, the goal here is too authenticate a client side app to a REST api using CORS with a local strategy --- i.e. username & password --- even if the convention above isn't possible.
To Accommodate Bounty:
This is a client side app, so let's stay trendy.
I'm looking for a working example using the Node.js OAuth 2.0 seed above for the API/Auth server and a front end framework like Angular.js or Backbone.js to make requests.
The example should match the context described above.
I'm working on an app with a pretty similar architecture though the services are .NET Web API rather than Node and we're using DotNetOpenAuth for the OAuth provider. Rather than the hybrid approach you're suggesting we're doing the following:
x.com serves up a login page
login page POSTs back credentials to x.com
server side logic at x.com combines client_id and client_secret with the credentials to submit a token request (resource owner password credentials grant that you've
mentioned above) receiving back both a temporary access token and a
refresh token
the refresh token is encrypted into a cookie issued by x.com
both the cookie (with encrypted refresh token) and the temporary access token are then sent to the browser
the client app (angular in my case) can now use the access token to hit api.x.com for services (It appears you're well aware of the limitations of CORS... we hacked a version of angular's $resource to facilitate this but it wasn't pretty since we wanted to use all HTTP verbs and support IE9)
when the access token expires, the client side app can request a new access token from x.com
server-side, x.com decrypts the cookie to get at the refresh token and issues another oauth call for a new access token
This is fairly high-level but hopefully gives you a sense for how to tackle your situation. In my case, and it appears in yours, we didn't want to use session state or a database to store the refresh token but obviously exposing that to the browser introduces security concerns so the encryption of the refresh token is important (among other security considerations) and the use of the cookie eliminates the need for session state or other persistent storage on x.com.
Not an answer running for the prize. Just my 2 cents :)
On my web server,
I do my authentication through a rest call with login/password with basic authentication over https. This call delivers a key to the client (a one page web app).
Then every subsequent REST call is signed with the key. The server checks that the signature is correct and everything still happen in https.
This mechanism is quite used I believe.
I don't see the issue with cross domain. I have a single source anf if I need something from another source, I'd use JSONP.
I use nginx as an https->http forwarder.
Not sure how it competes with an OAuth2 solution.
I've built this example using Node and PassportJS to show how to authenticate the users with Facebook or Local Strategy. Both sides are on different domains as you described and it requires CORS enabled.
GitHub: https://github.com/pablodenadai/Corsnection
Live demo: http://corsnection-client.herokuapp.com/
I can't promise that I have time to write working example but I can show you 2 paths :)
The biggest deal is CORS. After you solve that problem it is easy to use $http service. So, first and probably easiest may be to configure reverse proxy in x.com webserver which points to api.x.com. I wrote article here
Second approach is better, and created for exactly this purpose, to authorise specific domain to use your resource. It involves a bit of coding in api.x.com so you don't have to change anything in new web applications served in other domains. You simply need to authorise CORS requests in api.x.com service.
Create table in database where you can manage list of authorised domains
Add in that table record "x.com"
in api.x.com add request filter/interceptor what ever tech term you use for method which should be invoked after request is handled and add in response Access-Control-Allow-Origin: x.com if request comes from x.com (in other words check in request header refer value match to any value in table above and put that value in Access-Control-Allow-Origin response header).
That is all :) After this if you know how to use $http or jQuey.ajax you will be able to POST/PUT/DELETE/... any request to api.x.com from any authorised domain in just few minutes.
I very similar idea using vinilla js web app and cross domain authentication to GAE backend or OpenID connect.
The web app is run on CDN. When click login link, it goes to respective login server and redirect back to the web app (with XSRF security token and HTTPS only cookie). Login server accept cross domain request with credentials. XSRF token has to be set (in header) with every request. cookie is set by the browser. Since it is HTTP only cookie, JS cannot read it. The technique is very secure.
Once login, you can get secure assess from login server.
For detail description, you can find here and open source repo here.

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.

Categories

Resources