I have an an iframe, which uses MSAL authentication. it's parent also uses the same auth mechanism and once the user has logged in the parent app, I should be able to log him in the iframe with SSO. I tried doing it with loginHint but I get this error:
That's the piece of code in the iframe that receives the loginHint from the parent and trying to use it for SSO:
window.addEventListener("message", (event) => {
// check the origin of the data
if (event.origin === parentDomain) {
const loginHint = event.data;
// attempt SSO
publicClient
.ssoSilent({
scopes: ["openid", "profile", "User.Read"],
loginHint,
})
.then((res) => {
console.log(res);
})
.catch((error) => {
console.error(error);
});
}
});
I think it might have something to do with the fact that my organization is using MFA (Multi factor authentication), but I'm not exactly sure. Is there a way to bypass this
without canceling the MFA?
You cannot use interactive login inside IFRAME with MSAL, also embedded webviews can stop working for security reassons authenticathing against third party providers like Google or others (OpenID).
It's not about MFA.
CmsiInterrupt - For security reasons, user confirmation is required for this request. Because this is an "interaction_required" error, the client should do interactive auth. This occurs because a system webview has been used to request a token for a native application - the user must be prompted to ask if this was actually the app they meant to sign into. To avoid this prompt, the redirect URI should be part of the following safe list:
http://
https://
msauth://(iOS only)
msauthv2://(iOS only)
chrome-extension:// (desktop Chrome browser only)
Related
I've previously setup and followed these steps, and got it to work:
https://firebaseopensource.com/projects/firebase/quickstart-js/auth/chromextension/readme/#license
But cloning this extension, but with a different Extension ID, different firebase instance and OAuth/key setup, I've tried to follow the steps at least 3 separate times, but every time it fails at the last step, the login (the consent screen works though)
Upload a fresh dummy extension (without key field in manifest.json) (But it is the exact same code as the working one)
Get the Chrome Extension ID, and the Public Key, no problem
Create OAuth Client ID with the Extension ID, configured consent screen, no problem, the screen shows up and I can click through
Add OAuth & Public Key to manifest.json
Make another OAuth Client ID? (I think this is a duplicate step, because which Client ID should I use? and afaik the whitelisting is optional)
Use chrome.identity to get OAuth token:
export function startAuth(interactive) {
// Request an OAuth token from the Chrome Identity API.
chrome.identity
.getAuthToken({ interactive: !!interactive }, (token) => {
if (chrome.runtime && !interactive) {
console.error('It was not possible to get a token programmatically.');
}
else if (token) {
// Authorize Firebase with the OAuth Access Token.
const credential = firebase.auth.GoogleAuthProvider
.credential(null, token);
firebase.auth()
.signInWithCredential(credential)
.catch((error) => {
// The OAuth token might have been invalidated. Lets' remove it from cache.
if (error.code === 'auth/invalid-credential') {
chrome.identity
.removeCachedAuthToken({ token }, () => {
startAuth(interactive);
});
}
});
}
else {
console.error('The OAuth Token was null');
}
});
}
Note: This code is working with another extensionID/OAuth/key, so the code itself can't be the problem.
There isn't much to change between them, it's really the ExtensionID, the OAuth client ID url, and the public key.
I've followed the steps 3 times, to no avail, every time I get the error auth/invalid-credential. I do get a token though, but that token won't authenticate. How to find out why this is?
It's trying to post to this address, and returning error 400:
POST: https://identitytoolkit.googleapis.com/v1/accounts:signInWithIdp?key=xxxxx
Error: INVALID_IDP_RESPONSE : Invalid Idp Response: access_token audience is not for this project
My conclusion
There must be something changed with how to set this up, even though it works with different credentials
The problem was that I didn't create the OAuth in the same project in google cloud console, as the firebase project...
I have a SailsJS website for which I implemented authentication through a form where user needs to fill in email and password. copied from ActivityOverloard 2.0 example code
Login
login: function(req, res) {
console.log("Login hehe!!");
// Try to look up user using the provided email address
User.findOne({
email: req.param('email')
}, function foundUser(err, user) {
if (err) return res.negotiate(err);
if (!user) return res.notFound();
console.log("found email");
// Compare password attempt from the form params to the encrypted password
// from the database (`user.password`)
require('machinepack-passwords').checkPassword({
passwordAttempt: req.param('password'),
encryptedPassword: user.encryptedPassword
}).exec({
error: function(err) {
console.log("There was an error with password");
return res.negotiate(err);
},
// If the password from the form params doesn't checkout w/ the encrypted
// password from the database...
incorrect: function() {
console.log("Password doesn't checkout w/ the encrypted");
return res.notFound();
},
success: function() {
console.log("Good password");
var now = new Date();
User.update(user.id, { online: true, lastLoggedIn: now }, function() {
// Store user id in the user session
req.session.me = user.id;
User.publishUpdate(user.id, {
online: true,
id: user.id,
name: user.name,
lastLoggedIn: now,
action: ' has logged in.'
});
// All done- let the client know that everything worked.
return res.ok();
});
}
});
});
my page is protected with login
myPage: function(req, res) {
if (!req.session.me) {
return res.view('login'); // not authenticated will take you to the login page
}
// It's authenticated, it runs the code below
// DO SOMETHING
Now a very special use case, I need to open my page without user interaction (It can't be through a form) but I still need it to be protected. I'd need to pass some kind of access token.
I understand that passing an "access token" as query param is most probably not a good idea isn't it?
In fact, I don't know how to resolve my problem and allow to access myPage other than a session based authentication through a user interaction via a form ...
It seems to me that I'd need to first get a token programmatically and then open a browse to my page ... I bet there is some best practices to address my problem out there.
Any pointers? may be someone can fill the knowledge gap.
Realisticly speaking, you have multiple options with regards to passwordless or formless logins in node.js/express.js and therefore sails.js, as sails is built on top of both.
How you would approach the solution, really depends on the scale and use of your application/applications. For example; will the same login credentials be used to access multiple applications or a single web application, will the application be available only in an intranet or across the whole WWW.
Regardless of the scenario above, there will next to always be some initial setup required by the user, whether that is an initial sign up with an identity provider or an initial sign up with your application. The sign up form, will not dissappear entirely, rather it will become a one time event.
So let's look at some options and how we might introduce them into an express/sails application/s, I will start with the most basic and work down in difficulty.
Option 1:
Make use of the sails session store. From your code, you have already started doing this. The logic works something like this:
Your user signs up or logs in for the first time. At this stage you set the users session to be authenticated.
// Store user id in the user session
req.session.me = user.id;
req.session.authenticated;
You set a policy on all the pages which require authentication. Luckily, sails has already done some of the heavy lifting here by creating a sessionAuth policy in the folder api/policies. To make use of them open the config/policies.js file and add this policy to your protected pages
'my_app' : {
'route_to_protect' : 'sessionAuth'
},
Finally, you will want to make this session cookie last a really long time, to do this open config/session.js and edit the cookie maxAge to suit your needs. For example, if you want to force the user to login every 365 days, you might do some like this:
// milliseconds * seconds * minutes * hours * days
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 365
},
The draw back to this option is that your sessions will be lost if the application is restarted and all users will have to log in again.
Option 2:
Use a simple third party library like passwordless. Passwordless offers token-based authentication for express web applications and as sails is built on top of express...
The general jist of passwordless is when a user signs up, you deliver them a link to your application via email, this will log them in and in turn set up there session. Passwordless makes use of mongo as a session store, so you can either install mongo or use something like mLab which is a Mongo Database-as-a-Service provider. For a complete run through on using passwordless, take a look at their getting start page here.
Now for the more featureful based options.
Option 3:
If you are developing an application that is public facing, making use of Passport.js with sails is a great option.
Passport is authentication middleware for Node.js. Extremely flexible and modular, Passport can be unobtrusively dropped in to any Express-based web application. A comprehensive set of strategies support authentication using a username and password, Facebook, Twitter, and more.
Passport works with Sails just like it does with Express.
There are already a shed load of guides on setting up passport out in the ether. But a great step-by-step is available here and is also the one referenced by sails in there official documentation here.
Passport is in all essence an authentication middleware. It allows users to identify themselves based on this authentication, you can develop the correct authorization functionality in your application.
Option 4:
Make use of SAML or OAuth. From a development and implementation perspective, these are by far the biggest undertaking out of the options provided.
SAML and OAuth are authorization middleware which refers to rules that determine who is allowed to do what. Both have a very similar setup and make use of an Identity Provider(IdP) and Service Provider(SP), where the IdP represents an online service that authenticates users in the flow and the SP represents an application that relies on a trusted IdP for authentication and authorization.
I am more familiar with SAML, so what follows is with reference to considerations when implementing SAML in a project.
You will first need to register your application (SP) with an IdP. With regards to IdP's, what you choose is based on the scale and requirements of your application, there are free online IdP's like ZXIDP and SSOCircle or if your application required a dedicated IdP you could look at something like OpenSSO. You could also consider creating your own Node.js IdP using the saml-idp package.
Integrating SAML into a sails application is not overly difficult. Make use of the saml2-js package.
Once all configured, the logic works something like this.
User opens their web-browser and goes to yoururl.
To authenticate the user yoururl constructs a SAML Authnrequest, signs, encrypts and encodes it.
Then yoururl redirects the user's to the IdP to authenticate.
The IdP validates the request, in the first signup/login, the IdP will ask the user to enter their username and password, after that it will use the sessioning and other than the address change in the browser address bar the user will not see much.
If the user is successfully authenticated, the IdP generates a SAML token that includes information about the user (username, etc) and redirects them with this token back to yoururl.
Finally yoururl verifies the SAML token, extracts the identity information about the user including authorisations and logs them in.
I'm building a tool where a user would want to authenticate multiple Instagram accounts into the application. The problem I run into is if the user has already authenticated one and I initiate the OAuth dialogue again, the OAuth assumes that I want the access token of the user already logged in.
I have an iOS app that is similar and the way to avoid this is clear all the cookies of the Safari browser.
I'm using the instagram-node module right now.
app.get('/authenticateInstagram', function(req, res) {
res.redirect(ig.get_authorization_url(redirectURI, {
scope: ['basic', 'public_content', 'likes', 'follower_list', 'relationships'],
state: 'a state'
}));
});
app.get('/handleInstagramAuth', function(req, res) {
ig.authorize_user(req.query.code, redirectURI, function(err, result) {
if (err) {
console.log(err.body);
res.send('Didn\'t work');
} else {
console.log('Yay! Access token is ' + result.access_token);
res.send('You made it!!');
}
});
});
So when I try to add another IG account (now that I've signed into an Instagram account already), I don't get prompted to log in by the OAuth sequence. It assumes I'm the previously signed in user.
I can think of the following approaches
You can make a request to logout end point if Instagram supports it. Once you logout you can login again
I am not sure if instagram supports prompt=login parameter. If so it should take you to login page for each call.
I checked Instagram documentation and I think you can use Client side (Implicit) flow to login every time to get access token.
I'm building a Chrome extension and would like to use Firebase to persist state shared between users. Firebase authentication doesn't work within Chrome extension because there's no origin domain. The chrome.identity API can be used to ensure that the user is authenticated and to get the access token for OAuth requests.
A couple of considerations:
Use chrome.storage to store a token and use that to authenticate with Firebase. The storage area is not encrypted, so it would be trivial to read a user's token from their disk.
I assume the token returned by chrome.identity.getAuthToken is an OAuth access token and therefore transient - it wouldn't be suitable for a permanent unique identifier for a user.
I could make a request to a Google OAuth API to exchange the access token for the user's profile (https://www.googleapis.com/userinfo/v2/me), which contains an id field, but this is public.
I came across this question on my quest to solve a similar problem. I am sure the question is outdated but maybe my solution helps someone else stumbling over this question.
It is indeed possible to use chrome.identity for Firebase authentication... But the way is not through the custom authentication method. There is another method which accepts the OAuth2 token from chrome.identity.getAuthToken.
Here is everything I did following this tutorial:
(It also mentions a solution for non-Google auth providers that I didn't try)
Identity Permission
First you need permission to use the chrome identity API. You get it by adding this to your manifest.json:
{
...
"permissions": [
"identity"
],
...
}
Consistent Application ID
You need your application ID consistent during development to use the OAuth process. To accomplish that, you need to copy the key in an installed version of your manifest.json.
To get a suitable key value, first install your extension from a .crx file (you may need to upload your extension or package it manually). Then, in your user data directory (on macOS it is ~/Library/Application\ Support/Google/Chrome), look in the file Default/Extensions/EXTENSION_ID/EXTENSION_VERSION/manifest.json. You will see the key value filled in there.
{
...
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgFbIrnF3oWbqomZh8CHzkTE9MxD/4tVmCTJ3JYSzYhtVnX7tVAbXZRRPuYLavIFaS15tojlRNRhfOdvyTXew+RaSJjOIzdo30byBU3C4mJAtRtSjb+U9fAsJxStVpXvdQrYNNFCCx/85T6oJX3qDsYexFCs/9doGqzhCc5RvN+W4jbQlfz7n+TiT8TtPBKrQWGLYjbEdNpPnvnorJBMys/yob82cglpqbWI36sTSGwQxjgQbp3b4mnQ2R0gzOcY41cMOw8JqSl6aXdYfHBTLxCy+gz9RCQYNUhDewxE1DeoEgAh21956oKJ8Sn7FacyMyNcnWvNhlMzPtr/0RUK7nQIDAQAB",
...
}
Copy this line to your source manifest.json.
Register your Extension with Google Cloud APIs
You need to register your app in the Google APIs Console to get the client ID:
Search for the API you what to use and make sure it is activated in your project. In my case Cloud Firestore API.
Go to the API Access navigation menu item and click on the Create an OAuth 2.0 client ID... blue button.
Select Chrome Application and enter your application ID (same ID displayed in the extensions management page).
Put this client ID in your manifest.json. You only need the userinfo.email scope.
{
...
"oauth2": {
"client_id": "171239695530-3mbapmkhai2m0qjb2jgjp097c7jmmhc3.apps.googleusercontent.com",
"scopes": [
"https://www.googleapis.com/auth/userinfo.email"
]
}
...
}
Get and Use the Google Auth Token
chrome.identity.getAuthToken({ 'interactive': true }, function(token) {
// console.log("token: " + token);
let credential = firebase.auth.GoogleAuthProvider.credential(null, token);
firebase.auth().signInWithCredential(credential)
.then((result) => {
// console.log("Login successful!");
DoWhatYouWantWithTheUserObject(result.user);
})
.catch((error) => {
console.error(error);
});
});
Have fun with your Firebase Service...
I am working on a Chrome extension where the backend server requires offline access and a refresh token for the user's account. I am not familiar with Chrome extensions, and not sure how I should do the authorization in this case.
The benefit of using chrome.identity is that I can set interactive=false and attempt to authorize without bothering the user when the plugin loads. However, the getAuthToken() method doesn't provide a way for the backend to get a refresh token.
If, instead, I use the launchWebAuthFlow() method and redirect user back to OAuth page, i.e.
chrome.identity.launchWebAuthFlow({
url: https://*<my_host>*/auth/google,
interactive: true,
}, function(url) {
var uri = URI(url),
params = uri.search(true);
var token = params.authToken;
done(token);
});
where https://*<my_host>*/auth/google is my server OAuth path, then for some reason the Permissions scope page doesn't show up. Instead, it shows the Google login page. (Note: if I navigate to https://*<my_host>*/auth/google using the browser, it permissions page shows correctly.)
Is there anyway to do this with chrome.identity API? If not, do I have to do the OAuth using a popup? What's the proper way to do this normally?