G Suite identity provider for an AWS driven browser based App - firebase

I'm aware of how to create a Google authenticated app via with google-signin-client_id 3089273xx-xxxxxxxxxxxx.apps.googleusercontent.com & <script src="https://apis.google.com/js/platform.js" async defer></script>, but the problem here is that, I have not been able to LIMIT the login to just my company's G Suite instance.
The app I have is a "serverless" JS bundle hosted on S3. The logged in Google token is tied to an AWS role that accesses sensitive resources.
So typical solutions to check the email of googleUser.getBasicProfile() or pass a hd parameter don't make any security sense since they can be manipulated with browser dev tools IIUC.
Is there some other Google API I could be using or strategy I could apply? I imagine the solution would come in the form of a special google-signin-client_id for my company's domain which is hosted by G Suite. This is how it's tied to the role at AWS:
I'm aware I could setup duplicate my users in AWS "user pools" and use Cognito, but I am trying to have a "single source of truth" for the company's employees & ease the administration burden.

UPDATE: This answer is insecure as if you simply remove hosted_domain, you can authenticate with any Google login.
After straying upon https://developers.google.com/identity/work/it-apps & using GAPI directly I found I could do a
GAPI.auth2.init({
client_id: CLIENT_ID,
hosted_domain: 'example.com'
})
And then as the documentation advises, you setup Manage API client access
So now only users of #example.com on Gsuite can access this JS app! This took weeks to figure out. So just to conclude, how to authenticate using Google on a AWS powered serverless app:
Setup a client ID via OAuth client ID with your whitelisted origin URLs from https://console.developers.google.com/apis/credentials
In AWS IAM setup a Role with Google as the (web) Identity provider with the client ID
Add your client ID https://admin.google.com/AdminHome?chromeless=1#OGX:ManageOauthClients as documented here https://developers.google.com/identity/work/it-apps to crucially limit your application to your company's domain.
So now we have a statically hosted App limited to only company employees to access sensitive paid AWS APIs.

I tried 3 different options, the first one worked for my scenario:
First Option - Validating Google Id Token on each call on lambda side
I always pass the id_token as a header on the client calls(web and mobile apps).
"acceptableHds" Is the list of allowed domains.
const oauth = new Auth.OAuth2(CLIENT_ID_WEB, CLIENT_SECRET);
oauth.verifyIdToken(token, null, (err, ticket) => {
if (err) {
return reject(err);
}
const payload = ticket.getPayload();
const tokenIsOK = payload &&
payload.aud === CLIENT_ID &&
new Date(payload.exp * 1000) > new Date() &&
acceptableISSs.has(payload.iss) &&
acceptableHds.has(payload.hd)
return tokenIsOK ? resolve(payload.hd) : reject();
});
Second Option - Validating Google Id Token once on lambda side
I started this alternative way but I didn't finished because the first solutions fitted to my needs and the milestones was close(it needs a indentity pool):
1)Send the id_token to the lambda function and validate it on Google API(here is where you can check the domain using the code above)
2)Call the cognitoidentity.getOpenIdTokenForDeveloperIdentity on the lambda side using the id_token coming from the browser
3) On the client, call any of the Cognito or STS functions like assumeWebIdentity, AssumeRole using the tokens returned from getOpenIdToken.
function getCognitoToken(id_token) {
var param = {
IdentityPoolId: 'us-east-1:f7b3d55f-6b63-4097-be8f-3dc22ddec1a4',
Logins: { 'accounts.google.com': id_token }
}
return check_company(id_token).then(function (valid) {
return cognitoidentity.getOpenIdTokenForDeveloperIdentity(param).promise()
})
I couldn't finish the third step. You need use the tokens received on the second step without revealing the 'identity pool id'. If you do that and assure that the role can't list identity pool ids, it will work as intended and It will be secure.
Third Option - SAML provider
You can create a SAML provider and use SAML assertions to validate the user domain.
http://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_saml_assertions.html
I failed miserably trying to do it.
P.S.: Google Admin let you create private apps, limiting to you company domains, but It works only for mobile as far as I know
https://support.google.com/a/answer/2494992?hl=en
Hope it helps someone!

Related

Azure AD OAuth generates token for audience without permission

Despite reading multiple articles and tutorials at Microsoft.com, I am having an issue to understand how to define permissions between APIs using app registrations/OAuth2 in Azure AD. To exemplify, I have set up 2 simple app registrations in Azure AD, one for a back end API (lets say client ID A) and another for a front end or another API (client ID B). Then, I set up a basic .NET Core API with default (template) authentication (Options = tenant, client ID, etc) and the default weather forecast endpoint.
services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
.AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
services.AddControllers();
Right now, I am able to get a token from https://login.microsoftonline.com/<tenant>/oauth2/token with Client ID = B and resource = Client ID A, and when I send this token(with 'aud = A') to the API it accepts it.
Why is the token generated successfully, even though I have not set up any relationship between App Registrations A & B? The API Permissions tab in Azure AD is empty in both registrations - I thought AAD would reject the request stating that App B does not have access to App A. Or am I entirely responsible to validate audience claims via code on my app?
It is possible in Azure AD to acquire an access token through client credentials flow to an application that the client app has no permissions on.
This may be to enable some scenarios where the target API handles the whole authorization, but I am not sure.
I wrote an article some time ago on the need to check permissions always: https://joonasw.net/view/always-check-token-permissions-in-aad-protected-api.
I also wrote a follow-up after Microsoft addressed the cross-tenant ability to get access tokens: https://joonasw.net/view/cross-tenant-token-attacks-now-harder-in-azure-ad.
Because a client can get a valid access token without permission assignments,
authorization is crucial on your API side.
At our company we have a default authorization policy that checks every request that it contains either a valid delegated permission/scope or a valid application permission/app role.
That gives us a baseline that already protects the API from tokens like that.
It is usually not the only authorization applied.
In case delegated permissions are supported, you need to check that the user also has access to the thing they are trying to do.
Delegated/app permissions after all only say what the client app can do.

Can I use Firebase's authentication to protect data on my own private server?

Due to some constraints of my project I need to manage my api server and database separately from firebase, however there is nothing stoping me from using their other tools like analytics and in this particular case authentication.
I wanted to ask if following scenario is possible:
I authenticate user on my client app using their SDK's
I send relevant jwt data in headers of my API
Can I then somehow validate this on my server, so in essence avoid complex auth service and use firebase for this instead? Are there any examples, preferably for NodeJS server.
Yes you can, it's very common use case and firebase auth has been made with such usage in mind. As you said, send jwt data on headers, then on the server, you verify the token with firebase service:
var admin = require('firebase-admin');
admin.initializeApp(); //may require other steps, see last link
// idToken comes from the client app
admin.auth().verifyIdToken(idToken, true)//second argument is optional, checks whether the ID token was revoked
.then(function(decodedToken) {
let uid = decodedToken.uid;
// ...
}).catch(function(error) {
// Handle error
});
https://firebase.google.com/docs/auth/admin/verify-id-tokens
https://firebase.google.com/docs/reference/admin/node/admin.auth.Auth#verifyidtoken
https://firebase.google.com/docs/admin/setup

Transferring react client-side google sign-in to ASP.NET server-side

I am using react-google-login library to let users sign in my application on the client-side through Google, which works fine and returns token and profile information.
<GoogleLogin
clientId="XXXX.apps.googleusercontent.com"
buttonText="Login"
onSuccess={responseGoogle}
onFailure={responseGoogle}
cookiePolicy={'single_host_origin'}
/>
const responseGoogle = (response) => {
console.log(response);
}
Below is the configuration on the server to enable Google Authentication:
services.AddAuthentication()
.AddGoogle(options =>
{
IConfigurationSection googleAuthNSection =
Configuration.GetSection("Authentication:Google");
options.ClientId = googleAuthNSection["ClientId"];
options.ClientSecret = googleAuthNSection["ClientSecret"];
});
But, I do not how to transfer this successful login to the asp.net server-side. How can I use the token sent by google to sign in the user on the server side? I would like to do this for two cases: (a) the email in the system does not match the gmail, (b) the email in the system matches the gmail. I appreciate any help.
Small misunderstanding of the auth process. You can do it in a two ways:
client side OAuth (react-google-login)
server side OAuth (Microsoft.AspNetCore.Authentication.Google)
But you're trying to mix them in one, it's possible but not very useful, because it's harder to handle token expiration and also scopes for client side are limited in security reasons.
You need to go with next instructions to implement server side OAuth, and you don't need to use react-google-login:
Facebook, Google, and external provider authentication in ASP.NET Core
Google external login setup in ASP.NET Core

How to sync firebase users across several locations? (extension + website)

I'm working on chrome extension (provides main functionality) and the complementary website (mostly profile and billing related functionality) both backed with firebase backend.
I'm wondering if it's possible to implement the below scenario:
user logs in with the extension using firebase authentication (with firebaseUI lib)
I store a token that I can use to reauthenticate that user (is there such a token?)
when user opens the website, I login that user automatically with the token.
While both the extension and the website has their login/signup forms I'm wondering if it's possible to login user in the extension and to somehow automatically login that same user on the website so they don't have to enter their credentials twice?
So far I was hoping that I could use something like below:
firebase.auth().currentUser.getIdToken(true).then(function(idToken) {
console.log("idToken = ", idToken)
})
And then to use that idToken like this, since if I understand correctly, it's an AWT:
firebase.auth().signInWithCustomToken(idToken).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
console.log("signInWithCustomToken: error = ", error)
})
But it gives the following error:
code: "auth/invalid-custom-token"
message: "The custom token format is incorrect. Please check the documentation."
I can parse the token on https://jwt.io/ which shows all the user information but in the end it says "invalid signature"
So I guess this token can be only used to check authentication (like admin.auth().verifyIdToken(idToken)) but not to login user. Am I right?
I know I can create a custom token, but is there any straightforward way to workaround that and to login user from one place only using firebase funstionality? (of course without storing username/password)
You can't sign in with a Firebase ID token. What you can do is the following:
Keep the user session in the chrome extension and run all authenticated requests from there. Use postMessage (with origin verification) to talk with extension from app anytime a request is to be sent. With this you don't have to worry about session synchronization and no Firebase tokens are stored or passed to the web app or every web app that can access the extension.
Add a postMessage API to get an ID token from the extension after verifying the origin of the request. You can then send the request from the web app with the ID token. (less secure than 1 but easier to implement and session is stored in one place).
Create an HTTP endpoint that takes an ID token and returns a custom token. This would verifyIdToken and then create a corresponding custom token for that user using createCustomToken provided by Admin SDK. You then postMessage that from chrome extension to the web page after verifying origin and signInWithCustomToken with that custom token in that web app. This is the least secure as you are providing an endpoint to exchange a short lived ID token with an indefinite session. You will also deal with synchronization issues (user signs out from chrome extension, you have to sign out from websites, etc).

Google Smart Lock vs Credential Management API

I want to implement frictionless sign in process for my web app.
After some searching, I have found that there are two solutions available :
Google Smart Lock.
Credential Managment API.
My question is, What is the difference between the two API's (if any) and what are the possible use cases for both of these.
From what I have understood, both allow us to save account related info. But the advantage with smart lock is, that saved credentials can be used in corresponding android apps as well.
Thanks !
Note:
I intend to support login from multiple sources (google, facebook, linkedin etc.) , not just google.
TL;DR the one-tap sign-up / auto sign-in library includes Credential Management. You should probably just use the library: https://developers.google.com/identity/one-tap/web/get-started
Details
The JavaScript library supports account creation with Google Accounts (via a streamlined inline UX that can be shown on content pages instead user having to navigate to a traditional button-based UX and figure out which which button/option to pick and interact with pop-up/redirect)
And for returning users, the library allows you to programmatically retrieve on page load both tokens for existing one-tap / traditional Google Sign-In users as well as passwords via the Credential Management API in browsers that support it. You can do this with code such as the following:
const retrievePromise = googleyolo.retrieve({
supportedAuthMethods: [
"https://accounts.google.com",
"googleyolo://id-and-password"
],
supportedIdTokenProviders: [
{
uri: "https://accounts.google.com",
clientId: "YOUR_GOOGLE_CLIENT_ID"
}
]
});
retrievePromise.then((credential) => {
if (credential.password) {
// An ID (usually email address) and password credential was retrieved.
// Sign in to your backend using the password.
signInWithEmailAndPassword(credential.id, credential.password);
} else {
// A Google Account is retrieved. Since Google supports ID token responses,
// you can use the token to sign in instead of initiating the Google sign-in
// flow.
useGoogleIdTokenForAuth(credential.idToken);
}
}
See the documentation for details. The library does not currently support non-Google/password forms of identity, you'd have to implement sign-in flow with other mentioned identity providers SDKs yourself at the moment.
Also note that any sign-ins associated with a Google Account (OAuth token-based or stored and sync'ed password) will be available across Android and Chrome (and the rest of the for token-based accounts).
Please leave comments for any follow up questions.

Resources