Google web authorization broker and electron desktop app - c#

Background:
My project consists of a Vue 2 Front end encased in an electron shell with an asp.net core web API backend.
What I am trying to do is get a google authorization token and refresh token so that I can create a folder and files within that folder on a users google drive. I also want to display a list of the folders content in my front end.
I have tried using the new Google Identity Services code in my front end but when launching my app and clicking on the google button I the following error
Error 400: invalid_request
If you’re the app developer, make sure that these request details comply with Google policies:
redirect_uri: storagerelay://file/?id=auth12850
I think this is because electron is seen more of a desktop app which according to the google documentation needs to use a loopback address to open the system browser and authenticate from there.
So I tried the google authentication api on my backend which is C# I have the google web authorization broker setup and when I run my backend and call my endpoint I get the google sign in page and can get the authorization token and refresh token.
Question:
Is there a way to capture the URL of the page that comes up for authentication so that I can put it in a child window in electron.
or a way to pass the data to the front end so that I can show the list of files to my users.
Would I still need to use a loopback address even though I am getting the authentication page from the web broker?
If I do need the loopback functionality would I be better off using the data from the Google Desktop Application example rather than the google Web broker?
To learn more about electron you can go Electron website.

For a web app I think your going to have an issue
Users will be redirected to this path after they have authenticated with Google. The path will be appended with the authorization code for access, and must have a protocol. It can’t contain URL fragments, relative paths, or wildcards, and can’t be a public IP address.
Not just because of the format but because its going to need to be a domain you can register.
If you go with an installed app then the redirect uri is https://127.0.0.1
Im not sure how you are going to get this to route back properly.

I ended up using the nodejs google api to get this working this is my code that now returns an auth token and refresh token.
This code opens a child window when the authorize button is clicked and loads the google login/account select. Once authorized it shows the app permission window. When a user clicks allow the url is invoked in the loopback of the created server and the auth file is created in the specified directory.
part one successful.. now on to getting everything else working.
/* Google authentication */
function createGoogleWindow() {
const http = require('http');
const path = require('path');
const service = google.drive('v3');
const TOKEN_DIR = path.join(process.env.APPDATA, 'home-inventory', 'bin');
const TOKEN_PATH = path.join(TOKEN_DIR, 'home-inventory.json');
const querystring = require('querystring');
let googleWindow = new BrowserWindow({
parent: win,
height: 600,
width: 400,
webPreferences: {
webSecurity: false,
nodeIntegration: true,
enableRemoteModule: true,
contextIsolation: false
}
});
if (isDevelopment) googleWindow.webContents.openDevTools();
googleWindow.menuBarVisible = false;
googleWindow.on('closed', () => {
googleWindow = null;
});
const oauth2Client = new google.auth.OAuth2(
CLIENT_ID,
CLIENT_SECRET,
REDIRECT_URI
);
// check if we previously stored a token
fs.readFile(TOKEN_PATH, function (err, token){
if (err) {
getNewToken(oauth2Client);
} else {
oauth2Client.credentials = JSON.parse(token);
callback(oauth2Client);
}
});
const url = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES
});
function callback (auth) {
service.files.list({
auth: auth,
q: `name contains '.bak'`,
pageSize: 50,
fields: "nextPageToken, files(id,name,size,parents,createdTime)",
}, function(err, response) {
if (err) {
console.error('The API returned an error: ', err);
return;
}
const files = response.files;
if (files.length === 0) {
console.warn('no files found');
} else {
console.warn('files', files, auth.credentials.access_token);
}
});
}
function getNewToken(oauth2Client, callback) {
function storeToken(token) {
try {
fs.mkdirSync(TOKEN_DIR);
} catch (err) {
if (err.code !== 'EEXIST') {
throw err
}
}
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) {
throw err;
}
// console.debug('file was saved successfully');
googleWindow.close();
});
// console.warn('Token stored to: ', TOKEN_PATH);
}
function handler(request, response, server, callback) {
let qs = querystring.parse(require('url').parse(request.url).query);
oauth2Client.getToken(qs.code, function (err, tokens) {
if (err) {
console.error('Error getting oAuth tokens: ', err);
}
oauth2Client.credentials = tokens;
storeToken(tokens);
callback(oauth2Client)
server.close();
});
}
const server = http.createServer(function (request, response) {
handler(request, response, server, callback);
}).listen(8181, function() {
googleWindow.loadURL(url);
})
}
}

Related

Refresh token missing in Google Oauth response file

I'm implementing Google OAuth in ASP.Net MVC application using Google's OAuth .Net library. Below is my code.
IAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow(
new GoogleAuthorizationCodeFlow.Initializer {
ClientSecrets = new ClientSecrets {
** ClientId ** , ** ClientSecret **
},
DataStore = new FileDataStore( ** responsepath ** , true),
Scopes = new [] {
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/gmail.send"
},
Prompt = "select_account"
});
var userId = "user";
var uri = Request.Url.ToString();
var code = Request["code"];
if (code != null) {
var token = flow.ExchangeCodeForTokenAsync(userId, code, uri.Substring(0, uri.IndexOf("?")), CancellationToken.None).Result;
var oauthState = AuthWebUtility.ExtracRedirectFromState(flow.DataStore, userId, Request["state"]).Result;
Response.Redirect(oauthState);
} else {
var result = new AuthorizationCodeWebApp(flow, uri, uri).AuthorizeAsync(userId, CancellationToken.None).Result;
if (result.RedirectUri != null) {
Response.Redirect(result.RedirectUri);
}
}
When user click's Google sign-in button, my page is redirected to Google authentication page. After successful authentication, my page is displayed again. When I check the responsepath, below file is created which contains access token, expiry time, etc.
Google.Apis.Auth.OAuth2.Responses.TokenResponse-user
When I run the above code locally in my visual studio debugging environment (IIS express), the above response file has "refresh_token" in it. When the same code is deployed in production environment (IIS), the "refresh_token" is missing is response file. I would like to know the reason behind it since I need refresh token for further processing.
Note: In both the cases, I revoked the application's access from my Google account before trying. So, this is not about "refresh_token" will be sent only for the first time.
Adding prompt=consent parameter while sending request to Google gives refresh token every time without fail.

Express (NodeJS) failure to handle Post from Xamarin Android App

I've got a few devices that interact with an Express app on a local server. For the purposes of a prototype demo I'm trying to make this as simple as possible. Among many other things, my local server interacts with images, and image data in the /uploads folder thusly:
(Express/NodeJS code):
app.route('/uploads')
//Send File to Android
.get( function (req, res) {
var images = fs.readdirSync('./uploads');
console.log(images[images.length-1]);
var imgpath = path.resolve('./uploads/' + images[images.length-1]);
res.sendFile(imgpath);
//res.end('Image List Sent')
})
//Receive image chip data from Android
.post( function (req, res) {
Console.log("insideit");
Console.log(req.body);
res.end('got something?')
});
This server code is receiving requests from C# Android code. The GET command works perfectly, so I will omit that Xamarin/C# code. The POST command from the android app is thus (In C#/Xamarin):
var rxcui = "198840";
string _url = string.Format(#"http://10.1.10.194:3000/uploads", rxcui);
string datastr = "test";
try
{
(new WebClient()).UploadString(_url, datastr);
}
catch
{
Console.WriteLine("Post Upload Error");
}
}
The server sees the post request, but returns 500. It appears that it's not routing properly, because It won't go into my post handling code and print a simple test string. Any thoughts on why the POST command is not being handled appropriately?
Try to replace res.end with res.send(). Something like this
.post( function (req, res) {
Console.log("insideit");
Console.log(req.body);
res.send('got something?');
});
This should work.

How to allow users from only some specific tenants to be able to login to my app using Azure AD

I am building an SPA using AngularJs and using Azure AD for authentication purpose. My app is a multi-tenanted.
But, I can see that Azure AD is allowing users from any Tenant of any Active Directory to login to my app.
They are just presented with a consent screen (after successful login) where they are asked whether they are comfortable with providing access to their information from the app, and if they clicked yes, then they are freely redirected to my app's home page (if they have callback url to my app).
I tried modifying Manifest file of my app and adding some Tenants to the knownClientApplications property to allow users from only that Tenant but for no avail.
The AngularJs config code being:
adalProvider.init(
{
instance: AZURE_AD_INSTANCE,
tenant: 'common',
clientId: AZURE_AD_CLIENT_ID,
extraQueryParameter: 'nux=1',
//cacheLocation: 'localStorage', // enable this for IE, as sessionStorage does not work for localhost.
}, $httpProvider);
where, AZURE_AD_INSTANCE and AZURE_AD_CLIENT_ID are login url and app's client id respectively.
What could I do to allow users from only specific tenants and disallow others?
To only enable the specific tenants for the multi-tenant application, we need to verify the tenant ourselves.
Here is the code for your reference(adal-angular.js):
AdalModule.provider('adalAuthenticationService', function () {
...
var updateDataFromCache = function (resource) {
// only cache lookup here to not interrupt with events
var token = _adal.getCachedToken(resource);
// _oauthData.isAuthenticated = token !== null && token.length > 0;
_oauthData.isAuthenticated = isAuthenticated(token);
var user = _adal.getCachedUser() || { userName: '' };
_oauthData.userName = user.userName;
_oauthData.profile = user.profile;
_oauthData.loginError = _adal.getLoginError();
};
function isAuthenticated(token) {
console.log(token);
if (token !== null && token.length > 0) {
var decodedToken = _adal._extractIdToken(token);
var tenantIds = ["04e14a2c-0e9b-42f8-8b22-3c4a2f1d8802", "04e14a2c-0e9b-42f8-8b22-3c4a2f1d8801"];
var validateTenant = tenantIds.indexOf(decodedToken.tid) !== -1;
return tenantIds.indexOf(decodedToken.tid) !== -1;
} else
return false;
}
And to make users enable to login after they enter the wrong tenant account, we may custom the login method to append prompt=login.

how to send pushnotifications to firefox web browser?

I want to send pushnotifications to firefox web browser.......
<script>
function notifyMe() {
// Let's check if the browser supports notifications
if (!("Notification" in window)) {
alert("This browser does not support desktop notification");
}
// Let's check whether notification permissions have already been granted
else if (Notification.permission === "granted") {
// If it's okay let's create a notification
var notification = new Notification("Hi there!");
}
// Otherwise, we need to ask the user for permission
else if (Notification.permission !== 'denied') {
Notification.requestPermission(function (permission) {
// If the user accepts, let's create a notification
if (permission === "granted") {
var notification = new Notification("Hi there!");
}
});
}
// At last, if the user has denied notifications, and you
// want to be respectful there is no need to bother them any more.
} Notification.requestPermission().then(function (result) {
console.log(result);
}); function spawnNotification(theBody, theIcon, theTitle) {
var options = {
body: theBody,
icon: theIcon
}
var n = new Notification(theTitle, options);
}
</script>
whenever first time my website run permission popup will come.but when user click allow button how to get browser info like id.I want save that browser id into my database.next time I will send notification using that id. I did not find any code please help me.please anyone help me.
The Web Notifications API does not do what think it does.
It can only be used to display notifications from a web page that a user currently has open in their browser. It is comparable to alert(), except with a larger variety of formatting options.
It is not a push notification service, and cannot be used as one.

Google API Autentication - redirect_uri_mismatch error

I'm trying to authenticate my web app with Google Plus API. In the Google Developer console under Credentials for my Cliend Id for Web Application.
I add the following redirect uri: http://localhost:50883/oauth/add_oauth_token
When I run my application I get the following Error:
400. That’s an error.
Error: redirect_uri_mismatch
Application: SID3
You can email the developer of this application at: carlosmoralesdiego#gmail.com
The redirect URI in the request: http://localhost:55404/authorize/ did not match a registered redirect URI.
Learn more
Request Details
from_login=1
response_type=code
scope=https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile
access_type=offline
redirect_uri=http://localhost:55404/authorize/
as=-45fed094242eac62
pli=1
client_id=389029010035-knoo3a2445r77pirc06h8fhgdc5q0dsv.apps.googleusercontent.com
authuser=0
hl=es
So for any reason google changes my redirect uri to the port 55404, why?. Thanks so much and regards
This is the code:
protected void LoginToGooglePlus(object sender, ImageClickEventArgs e)
{
PlusService service = null;
UserCredential credential = null;
string[] scopes = new string[] {PlusService.Scope.PlusLogin, PlusService.Scope.UserinfoEmail,
PlusService.Scope.UserinfoProfile};
// here is where we Request the user to give us access, or use the Refresh Token that was previously stored in %AppData%
try
{
// here is where we Request the user to give us access, or use the Refresh Token that was previously stored in %AppData%
UserCredential credentials;
using (var stream = new FileStream("C:\\Users\\Usuario\\Documents\\Visual Studio 2010\\Projects\\WebApplication1\\WebApplication1\\client_secret.json", FileMode.Open, FileAccess.Read))
{
credentials= GoogleWebAuthorizationBroker.AuthorizeAsync(GoogleClientSecrets.Load(stream).Secrets, scopes, Environment.UserName, CancellationToken.None, new FileDataStore("WebApplication1")).Result;
}
}
catch (Exception ex)
{
//If the user hits cancel you wont get access.
if (ex.InnerException.Message.IndexOf("access_denied") != -1)
{
Console.WriteLine("User declined access");
Console.ReadLine();
return;
}
else
{
Console.WriteLine("Unknown Authentication Error:" + ex.Message);
Console.ReadLine();
return;
}
}
// Now we create a Google service. All of our requests will be run though this.
service = new PlusService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "SID3",
});
}
The json file which I use it's this one:
{"web":{"auth_uri":"https://accounts.google.com/o/oauth2/auth","client_secret":"jpkVDaaMjlLCHGh67DJ9Zv19","token_uri":"https://accounts.google.com/o/oauth2/token","client_email":"389029010035-knoo3a2445r77pirc06h8fhgdc5q0dsv#developer.gserviceaccount.com","redirect_uris":["http://localhost:50880/Paneles.aspx"],"client_x509_cert_url":"https://www.googleapis.com/robot/v1/metadata/x509/389029010035-knoo3a2445r77pirc06h8fhgdc5q0dsv#developer.gserviceaccount.com","client_id":"389029010035-knoo3a2445r77pirc06h8fhgdc5q0dsv.apps.googleusercontent.com","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs"}}
I just solved the same problem simply by changing DataStore to null.
credentials= GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
scopes,
Environment.UserName,
CancellationToken.None,
null // <-- here
).Result;
I don't know why but it stops the random port shenanigans.
I should also add, that in my case, it just solved one problem since I needed to publish my app online, where this solution was just wrong. If anyone needs the online version (asp.net 4.5 MVC 5) you can use the code here.
API redirect URL and your application Redirect URL Should be same....
For example (if your application URL Like this localhost:55404/authorize/ and need to add same URL in API Redirect URL localhost:55404/authorize/ )

Categories

Resources