chrome.identity.LaunchWebAuthFlow: How to implement logout in a web app using oauth2 - google-chrome-extension

I am working on some client side web app like a chrome extension that needs access to outlook mail and calendar. I followed the instruction on https://dev.outlook.com/RestGettingStarted and successfully got access and refresh tokens to retrieve data.
However, I cannot find any way of implementing "logout". The basic idea is to let user sign out and login with a different outlook account. In order to do that, I removed cached tokens, requested access tokens in interactive mode. The login window did pop out, but it took any valid email address, didn't let me input password and finally returned tokens for previous account. So I was not able to really use a different account until the old token expired.
Can anyone please tell me if it is possible to send a request to revoke the tokens so people can use a different account? Thanks!
=========================================================
Update:
Actually it is the fault of chrome.identity api. I used chrome.identity.LaunchWebAuthFlow to init the auth flow. It caches user's identity but no way to remove it. So we cannot really "logout" if using this api.

I used two logouts via launchWebAuthFlow - first I called the logout link to my app, then secondly, I called the logout link to Google.
var options = {
'interactive': false,
'url': 'https://localhost:44344/Account/Logout'
}
chrome.identity.launchWebAuthFlow(options, function(redirectUri) {});
options = {
'interactive': false,
'url': 'https://accounts.google.com/logout'
}
chrome.identity.launchWebAuthFlow(options, function(redirectUri) {});

Related

Unattended authorization into API - no longer possible?

A brief investigation shows that there once existed an authorization mode known as SOBO (for example, see docusign send on behalf functionality), which I find useful in a scenario when an application associates signing requests not with a logged-on user but with some other user. However I am unable to find any mention of it in current documentation; on the contrary, documentation clearly says only 3 grant types are supported, and all three involve impersonated user's actively expressing his consent. No way to just send user credentials, or, alternatively, have user express his consent just once on the docusign admin page, then reuse that consent for, say, one year or forever. Or some other way to avoid end user interaction.
Also, a requirement of using redirect URI to send back continuation data implies one has to either use on-premise solution or make one's application visible on the internet. No way to use public docusign service with on-premise application, right?
DocuSign is discouraging Legacy Header authentication because it requires your integration to hold on to the user's plain-text password. It also cannot support accounts that leverage Single-Sign On or Two-Factor Authentication. The long-term plan is likely to eventually retire it entirely, but there's no timeline for that. Documentation has been retired, but integrations that have used it in the past can still do so.
JWT auth is the equivalent replacement. With individual consent, a user grants the integration access once. Unless the user revokes that consent, the integration will be able to generate access tokens to act as the user indefinitely - JWT consent does not expire.
If you have an Organization with a Claimed Domain, an org admin can grant blanket consent to allow an integration to act as any user under that domain. If you'd like to grant consent to an integrator key owned by your organization, it's as simple as navigating to Org Admin > Applications > Authorize Application. Granting consent to a 3rd party app is similar to the Individual Consent workflow, but has extra scopes as documented here: https://developers.docusign.com/esign-rest-api/guides/authentication/obtaining-consent
Note that while JWT auth does require a redirect URI to be registered, an integration doesn't necessarily need to 'catch' the user after they've granted consent. While it would be recommended that the landing page trigger the user to move forward in the workflow, it's acceptable to point your redirect URI to https://www.example.com, grant consent, and then generate an access token.
I know this question has already been answered, but I'll post this answer here just in case someone still needs to do this. This method does not require user's consent. The below is Node.js / JS but can be easily translated into whatever language with the basics below.
// set default authentication for DocuSign; pulls data from this account
var auth = {
'Username': '(user email)',
'Password': '(user password)',
'IntegratorKey': '(api key found in admin)',
};
var options = {
'uri': 'https://www.docusign.net/restapi/v2/login_information',
'method': 'GET',
'body': '',
'headers': {
'Content-Type': 'application/json',
// turns the auth object into JSON
'X-DocuSign-Authentication': JSON.stringify(auth)
}
};
// send off your request using the options above
The above returns a response:
{
"loginAccounts": [
{
"name":"Your Company Name",
"accountId":"0000000",
"baseUrl":"https://{your_subdomain}.docusign.net/restapi/v2/accounts/0000000",
"isDefault":"true",
"userName":"User's Name",
"userId":"(36 character UUID)",
"email":"user#example.com",
"siteDescription":""
}
]
}
At this point, you can have to save the baseUrl and accountId that is returned. For the baseUrl, you only need to save the the sub-domain and domain url (https://{your_subdomain}.docusign.net), not the url paramters after that.
Now you can have enough information to make requests. The below example request pulls all the templates under this account.
var options = {
'uri': baseUri+'/accounts/'+accountId+'/templates',
'method': 'GET',
'body': '',
'headers': {
'Content-Type': 'application/json',
// turns the auth object into JSON
'X-DocuSign-Authentication': JSON.stringify(auth)
}
};
// send off your request using the options above

Asking for user info anonymously Microsoft Graph

In an old application some people in my company were able to get info from Microsoft Graph without signing users in. I've tried to replicate this but I get unauthorized when trying to fetch users. I think the graph might have changed, or I'm doing something wrong in Azure when I register my app.
So in the Azure portal i have registered an application (web app), and granted it permissions to Azure ad and Microsoft graph to read all users full profiles.
Then I do a request
var client = new RestClient(string.Format("https://login.microsoftonline.com/{0}/oauth2/token", _tenant));
var request = new RestRequest();
request.Method = Method.POST;
request.AddParameter("tenant", _tenant);
request.AddParameter("client_id", _clientId);
request.AddParameter("client_secret", _secret);
request.AddParameter("grant_type", "client_credentials");
request.AddParameter("resource", "https://graph.microsoft.com");
request.AddParameter("scope", "Directory.Read.All");
I added the last row (scope) while testing. I still got a token without this but the result is same with or without it.
After I get a token I save it and do this request:
var testClient = new RestClient(string.Format("https://graph.microsoft.com/v1.0/users/{0}", "test#test.onmicrosoft.com")); //I use a real user here in my code ofc.
testRequest = new RestRequest();
testRequest.Method = Method.GET;
testRequest.AddParameter("Authorization", _token.Token);
var testResponse = testClient.Execute(testRequest);
However now I get an error saying unauthorized, Bearer access token is empty.
The errors point me to signing users in and doing the request, however I do not want to sign a user in. As far as i know this was possible before. Have Microsoft changed it to not allow anonymous requests?
If so, is it possible to not redirecting the user to a consent-page? The users are already signed in via Owin. However users may have different access and i want this app to be able to access everything from the azure ad, regardless of wich user is logged in. How is the correct way of doing this nowadays?
Or am I just missing something obvious? The app has been given access to azure and microsoft graph and an admin has granted permissions for the app.
Edit: just to clarify, i tried both "Authorization", "bearer " + _token.Token, and just _token.Token as in the snippet.
Yes, it's still possible to make requests to Graph without a user present using application permissions. You will need to have the tenant admin consent and approve your application.
Edit / answer: Adding the 'Authorization' as a header instead of a parameter did the trick. It works both with 'bearer token' and just 'token'

How implement an access token, so I can bypass login page (sailsjs)

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.

.NET Gmail OAuth2 for multiple users

We are building a solution that will need to access our customers Gmail accounts to read/send mail. On account signup, we'd have a pop-up for our customer to do Gmail auth page and then a backend process to periodically read their emails.
The documentation doesn't seem to cover this use case. For example https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth says that client tokens should be stored in client_secrets.json - what if we have 1000s of clients, what then?
Service accounts are for non-user info, but rather application data. Also, if I use the GoogleWebAuthorizationBroker and the user has deleted access or the tokens have expired, I don't want my backend server app to pop open a web brower, as this seems to do.
I would imagine I could use IMAP/SMTP accomplish this, but I don't think it's a good idea to store those credentials in my db, nor do I think Google wants this either.
Is there a reference on how this can be accomplished?
I have this same situation. We are planning a feature where the user is approving access to send email on their behalf, but the actual sending of the messages is executed by a non-interactive process (scheduled task running on an application server).
I think the ultimate answer is a customized IAuthorizationCodeFlow that only supports access with an existing token, and will not execute the authorization process. I would probably have the flow simulate the response that occurs when a user clicks the Deny button on an interactive flow. That is, any need to get an authorization token will simply return a "denied" AuthorizationResult.
My project is still in the R&D phase, and I am not even doing a proof of concept yet. I am offering this answer in the hope that it helps somebody else develop a concrete solution.
While #hurcane's answer more than likely is correct (haven't tried it out), this is what I got working over the past few days. I really didn't want to have to de/serialize data from the file to get this working, so I kinda mashed up this solution
Web app to get customer approval
Using AuthorizationCodeMvcApp from Google.Apis.Auth.OAuth2.Mvc and documentation
Store resulting access & refresh tokens in DB
Use AE.Net.Mail to do initial IMAP access with access token
Backend also uses AE.Net.Mail to access
If token has expired, then use refresh token to get new access token.
I've not done the sending part, but I presume SMTP will work similarly.
The code is based on SO & blog posts:
t = EF object containing token info
ic = new ImapClient("imap.gmail.com", t.EmailAddress, t.AccessToken, AuthMethods.SaslOAuth, 993, true);
To get an updated Access token (needs error handling) (uses the same API as step #1 above)
using (var wb = new WebClient())
{
var data = new NameValueCollection();
data["refresh_token"] = refresh;
data["client_id"] = "(Web app OAuth id)";
data["client_secret"] = "(Web app OAuth secret)";
data["grant_type"] = "refresh_token";
var response = wb.UploadValues(#"https://accounts.google.com/o/oauth2/token", "POST", data);
string Tokens = System.Text.Encoding.UTF8.GetString(response);
var token = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(Tokens);
at = token.access_token;
return at;
}

How to enable server side offline access for Chrome Extension OAuth?

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?

Resources