I am a student working on a web app that allows users to upload videos to their youtube channel. It is working great, but I would like the users to be able to revoke that access if they want to.
For now, I am only deleting the user's access token from the database, but the app is still showing in the user's Google "Apps with access to your account" page... Is there a way to revoke that access without the user having to manually go to that page and click on the "Remove access" button?
Here is an example of manual access removal. I would simply like to have such a "remove access" button in my web app that would do the same. Is there a way?
Thanks for your help!
Take the access token you have for the user and just send a request to the revoke endpoint. It will remove the users access to your app.
curl -d -X -POST --header "Content-type:application/x-www-form-urlencoded" \
https://oauth2.googleapis.com/revoke?token={token}
Assuming you are using the Google api .net client library there should be a revoke method already
UserCredential cred = await GoogleWebAuthorizationBroker.AuthorizeAsync(
Helper.GetClientSecretStream(), new string[] { "email" },
"user", default, new NullDataStore());
Assert.NotNull(cred);
var accessToken = await cred.GetAccessTokenForRequestAsync();
using (var f = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecretsStream = Helper.GetClientSecretStream()
}))
{
// Succeeds if no exception is thrown.
await f.RevokeTokenAsync("a-user", accessToken, default);
// Cannot verify revocation, as it takes in indeterminate duration to propagate.
}
Related
Haiya all! I have the following code that sets up my Google API:
// Open the FileStream to the related file.
using FileStream stream = new("Credentials.json", FileMode.Open, FileAccess.Read);
// The file token.json stores the user's access and refresh tokens, and is created
// automatically when the authorization flow completes for the first time.
UserCredential credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.FromStream(stream).Secrets,
new[] { SheetsService.Scope.Spreadsheets, YouTubeService.Scope.YoutubeReadonly },
"admin",
CancellationToken.None,
new FileDataStore("Token", true),
new PromptCodeReceiver()
);
// Create Google Sheets API service.
builder.Services.AddSingleton(new SheetsService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = name,
}));
// Create Youtube API service.
builder.Services.AddSingleton(new YouTubeService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = name,
}));
With this code, all my services are injected properly into the service provider I'm using. However, after 7 days of being up the ASP.Net website seems to not refresh the token, even though my token file has a refresh token. This is, as I would imagine, due to the fact OAuth tokens only last 7 days.
So, how would I get Google's API to automatically refresh my token? As this is a website, it needs to be online for long amounts of time without going down due to an expired token.
If your app is still in testing refresh tokens expire after 7 days. To have them expire longer you will need to set your app into production, and possibly verify it.
Refresh token expiration
A Google Cloud Platform project with an OAuth consent screen configured for an external user type and a publishing status of "Testing" is issued a refresh token expiring in 7 days.
web app vs installed app
I'm surprised that your code is working hosted on a website.
GoogleWebAuthorizationBroker.AuthorizeAsync is intended for installed applications. Its not going to work for Asp .net hosted on a web site. The reson it wont work is that it causes the consent browser to open on the machine the code is running on. This will work in development but as soon as you try to host it on a webserver its not going to work as the server cant spawn a local web browser.
You should be following Web applications (ASP.NET Core 3)
Which will use dependency injection
public void ConfigureServices(IServiceCollection services)
{
...
// This configures Google.Apis.Auth.AspNetCore3 for use in this app.
services
.AddAuthentication(o =>
{
// This forces challenge results to be handled by Google OpenID Handler, so there's no
// need to add an AccountController that emits challenges for Login.
o.DefaultChallengeScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme;
// This forces forbid results to be handled by Google OpenID Handler, which checks if
// extra scopes are required and does automatic incremental auth.
o.DefaultForbidScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme;
// Default scheme that will handle everything else.
// Once a user is authenticated, the OAuth2 token info is stored in cookies.
o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddGoogleOpenIdConnect(options =>
{
options.ClientId = {YOUR_CLIENT_ID};
options.ClientSecret = {YOUR_CLIENT_SECRET};
});
}
You dont need to worry about refreshing your access token the library will handle that for you.
Is the idea to let the ASP.NET app access Youtube and Sheets...
on behalf of the end user or
on behalf of itself (no end user involved?
In case of (1), using GoogleWebAuthorizationBroker to start an OAuth authorization flow is the right way to go, but you probably shouldn't store the refresh token permanently. Either store it for the duration of the end user session only, or don't store it at all and trigger a new OAuth authorization flow every time the access token expires.
In case of (2), you should use a service account and load credentials by using GoogleCredentials.GetApplicationDefaultAsync(). If the app runs on Google Cloud, attach a service account to the underlying compute resource; if it's running elsewhere you can use Workload identity federation or service account keys.
Is it possible to get Facebook Profile Picture and user friends using Azure Mobile services Claims or any other way?
On the azure backend as shown below,
User picture is missing there if it is not public profile. It looks
like public profile is only link to facebook profile
I set user_friends but when I request identites with the code below,
i see 9 claims (name, surname, fullname, gender, profile etc.) but I
dont get any friends information.
Code I am using is simply
identities = await App.Client.InvokeApiAsync<List<AppServiceIdentity>>("/.auth/me");
According to the documentation, it should return what i set in the backend as dictionary of claims type and value.
I am able to achieve this using simple http request as below
var requestUrl = $"https://graph.facebook.com/v2.11/me/friends?fields=id,picture.width(72).height(72),name&access_token=" + accessToken;
But i want a general solution using azure backend if possible?
AFAIK, your could only retrieve the basic profile (name, picture, email,etc.) from the current authenticated user. Per my understanding, if you hope your mobile app backend would handle the requests against the Graph API of facebook stead of retrieving the access token and send request in your mobile client, you could create a custom Web API and use the following code to get the access token and send request in your mobile backend:
// Get the credentials for the logged-in user.
var credentials =
await this.User
.GetAppServiceIdentityAsync<FacebookCredentials>(this.Request);
if (credentials.Provider == "Facebook")
{
// Create a query string with the Facebook access token.
var requestUrl = $"https://graph.facebook.com/v2.11/me/friends?fields=id,picture.width(72).height(72),name&access_token=" + credentials.AccessToken;
// Create an HttpClient request.
var client = new System.Net.Http.HttpClient();
// Request the current user info from Facebook.
var resp = await client.GetAsync(requestUrl);
resp.EnsureSuccessStatusCode();
// Do something here with the Facebook user information.
var fbInfo = await resp.Content.ReadAsStringAsync();
}
For your mobile client, you could leverage InvokeApiAsync to call the related custom Web API provided by your mobile backend. More details, you could refer to How to: Work with authentication and Custom HTTP Endpoints.
Heres my problem:
I wan't to be able to create new users for my website, from my website. This is only aloud though, if I have the "isAdmin" flag set to true in the realtime db under /users/myid.
Generally I would have done this with a security rule but the problem here is that I need to use the admin SDK, since the normal "createNewUser" method signs in to the newly created user automatically. I as an admin though, want to be able to create a new user and stay logged in as myself. So what I wan't to do is use ajax post request to my server with my uid und the new userdata which is to be created. My server then checks if the given uid has the isAdmin flag and if so creates the user with the firebase admin SDK which provides such a method.
But, anyone, if they have an admins uid, could hit up that request and create a new user. (My clients definetely get uid's from other users).
So how would I go about proving to the server that I am actually logged in with that uid.
From my understanding, tokens are used to be able to write to the database, but I don't need that permission, I just need to prove that I'm actually logged in with that uid.
Is there something I'm missing? Thanks a lot guys!
Was easier then I thought. This will generate a token on the client side:
firebase.auth().currentUser.getToken(true).then(function(token) {
// send request to server with generated token
}).catch(function(error) {
// handle error
});
Which I can then verify on the server like so:
admin.auth().verifyIdToken(idToken)
.then(function(decodedToken) {
var uid = decodedToken.uid;
// user is logged in
}).catch(function(error) {
// user is not logged in, or other error occured
});
Taken from https://firebase.google.com/docs/auth/admin/verify-id-tokens
I'm trying to connect to the second Firebase app and authenticate with signInWithCredential(), but I don't know how to get valid idToken for the second app:
connect(accessToken: string, config: FirebaseAppConfig) {
let one: firebase.app.App = this.angularFireTwo.database["fbApp"];
one.auth().currentUser.getToken()
.then(idToken => firebase.auth.GoogleAuthProvider.credential(idToken, accessToken))
.then(credential => {
let two = firebase.initializeApp(config, `[${config.apiKey}]`);
return two.auth().signInWithCredential(credential);
})
.catch(console.warn)
.then(console.info);
}
I'm getting and error from https://www.googleapis.com/identitytoolkit/v3/:
Invalid id_token in IdP response
If I use signInWithPopup() I can authenticate and connection is working:
two.auth().signInWithPopup(new firebase.auth.GoogleAuthProvider())
Anyone knows what should I do to get valid idToken?
UPDATE:
I've been trying to figure out authentication process and, as far I understand it , it's something like this:
from config: FirebaseAppConfig firebase reads apiKey and authDomain
it contacts the servers and gets Web Client ID for enabled Google provider 123.apps.googleusercontent.com
with this Web Client ID and authDomain it contacts www.googleapis.com, which returns idToken
this idToken is then used to identify the app that's asking user for permission to access user's profile, etc.
when user agrees, callback returns user details + credential used for this authentication, which contains idToken of the web app and accessToken of the user
Now, if I use signInWithPopup() steps 2-3-4 are done in the background (popup window). I just need a way to generate idToken for the step 4, so I can use it to generate credential firebase.auth.GoogleAuthProvider.credential(idToken, accessToken) and sign-in using signInWithCredential().
I have access to everything I need to sign-in to the second app - it's; apiKey, authDomain, Web Client id 456.apps.googleusercontent.com, and user's unique accessToken.
But still can't figure out how to do it. I tried white-listing apps' one and two Web client IDs in their auth configurations, hoping that will allow them to accept each others idTokens, but that didn't work...
When you call:
firebase.auth.GoogleAuthProvider.credential(idToken, accessToken))
The first parameter should be a Google OAuth Id token. You are using the Firebase Id token and that is why you getting the error. Besides, if you are already logged in, why are you logging in again with signInWithCredential?
If you need to sign in with a Google credential you need either a Google OAuth Id token or a Google OAuth access token.
To duplicate Firebase OAuth sign-in state from one app to another, you get the credential from signInWithPopup result and use it to signInWithCredential in the second instance.
two.auth().signInWithPopup(new firebase.auth.GoogleAuthProvider())
.then(function(result) {
return one.auth().signInWithCredential(result.credential);
});
Suppose, for the sake of argument, that I already have a facebook access token for a user of my application. In that case, I don't really need to go through Firebase's whole auth.login("facebook") process, I really just want a trusted server to make sure this is a real access token (e.g. by making a GET request to "https://graph.facebook.com/me" with it) and then to set the Firebase user ID appropriately. Can Firebase do this?
Firebase Simple Login was recently updated to support logging in with an existing Facebook access token.
This means that you can integrate directly with then native Facebook JS SDK in your application, and then pass that Facebook access token to Firebase Simple Login (skipping a second pop-up) via:
var ref = new Firebase(...);
var auth = new FirebaseSimpleLogin(ref, function(error, user) { ... });
auth.login('facebook', { access_token: '<ACCESS_TOKEN>' });
See the access_token option on https://www.firebase.com/docs/security/simple-login-facebook.html for more information.