How to get Google's OAuth API to refresh token automatically? - asp.net

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.

Related

.net6 web app - Google API login using access token from Identity login (Google external login)

TL;DR
How do I retrieve the access_token from an Identity Google external login from (say) a controller?
Full details:
I'm working on a .net6 website with Identity and Google external login.
I have added some Google API (Drive and Youtube) functionality which worked fine locally using GoogleWebAuthorizationBroker.AuthorizeAsync to get the credentials to instantiate the services for Drive and Youtube.
When deployed to the server, I got errors due to the fact that the server doesn't support GoogleWebAuthorizationBroker.AuthorizeAsync, and found out I need to use OAuth2 as described here: https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth
The problem is that I am already using Identity, so cannot follow that documentation by the letter (I cannot get an instance of IGoogleAuthProvider injected in my classes as I would if I had .AddGoogleOpenIdConnect in my services)
I have tested that initializing the Drive and Youtube services with the access token I get from the Identity Google external logins works fine:
var token = "<ACCESS_TOKEN_VALUE>"
var credential = GoogleCredential.FromAccessToken(token);
_driveService = new DriveService(new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = "MyWebApp"
});
The problem is that being a website, I would have to store the refresh tokens and access tokens for all users somewhere (i.e. refresh token to db, and access token to session or in the Identity.Claims)
In my .AddGoogle() call I have the option googleOptions.SaveTokens = true; enabled, but I do not know how I can retrieve this tokens from.
Is there a proper way of storing/retrieving that token?

Authorization code flow in asp.net core and Is4 - jwt and cookies

I have simply basic solution for asp.net core web app and Is4.
In asp.net core I use addCookies() method, but the is4 returns me jwt token, why there is jwt when I use cookies?
I figure out it when I call
HttpContext.getTokenAsync(openIdconnectparameterNames.idToken)
Here I get jwt.. Can someone explain me why I get JWT if I'm using cookies?
I will try to explain how authentication using OpenId Connect Authorization Code flow is usually implemented for an asp.net core web app.
When an unauthenticated user (no cookie) tries to access your web app, the web app initiates the authorization code flow in order to authenticate the user. During the flow, the user is prompted to log in and then at some point, if login was successful, the identity provider (IdentityServer4) sends an ID token and an access token to the web app as you can also see in the diagram below:
The ID token contains user profile information. This is how the web app knows which user logged in. The access token can be used to get additional user information by calling the User Info Endpoint of Identity Server. Now the web app creates a cookie that contain all necessary user information, so that the next time he tries to access the web app, it won't have to initiate the whole authorization flow again. The web app will be able to identify if user is authenticated by validating the cookie.
The whole id token and access token are saved inside the cookie if you set SaveToken property to true in AddOpenIdConnect options.
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
//...
// access token is needed to make this request to identity server
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
});
Then you can read the tokens from cookie using
var accessToken = await HttpContext.GetTokenAsync("access_token");
var idToken = await HttpContext.GetTokenAsync("id_token");
Saving the tokens in the cookie is not necessary and you shouldn't do it if you don't need the tokens anymore after authentication (for example you might need the access token to access an API: https://docs.identityserver.io/en/latest/quickstarts/3_aspnetcore_and_apis.html).
https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow

Firebase admin - get Google OAuth token

I have a web application where users can sign in with Google.
To the sign-in process, I add a scope to be able to access Google Calendar.
Now that the user is signed in, I would like to - in server-side - get their current Google access token in order to make a request and get a list of their events.
Is there a way to get the current OAuth token (no need for refresh token) in order for me to make this completely on the server-side?
I'd say that you can check this article and put special attention to the recommendation for websites.
I understand you have configured already the consent screen, which is the first step of the basic steps on using OAuth 2.0. So I understand that you only have to perform the following steps:
Obtain an access token from the Google Authorization Server
Examine scopes of access granted by the user.
Send the access token to an API
I think you can also give a look to this other doc for more GCP insights over your goal to authorize the request using user tokens
Edited:
Regarding the Firebase Authentication, I understand this happens at the user's device, and you could use some code to retrieve the token and then send it to your back end servers as mentioned in here.
As a sample here there's the sample code for retrieving the token in Android:
FirebaseUser mUser = FirebaseAuth.getInstance().getCurrentUser();
mUser.getIdToken(true)
.addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
public void onComplete(#NonNull Task<GetTokenResult> task) {
if (task.isSuccessful()) {
String idToken = task.getResult().getToken();
// Send token to your backend via HTTPS
// ...
} else {
// Handle error -> task.getException();
}
}
});
A little about OAuth 2.0
Whenever a user signs up to your app/website via Google or 3rd Party, an Authorization Code, this Authorization Code is exchanged for an AccessToken & RefreshToken.
The AccessToken sent via Google are valid generally for 60 minutes.
Offline Access (Server Side)
Let's break it down to two parts:
If your need to update within 60 minutes of user's last activity
You can use firebase along with gapi to achieve that. You'll be provided with the AccessToken that can be sent back to server to add to calendar.
More info on implementation
If you need to update after 60 minutes of user's last activity
Firebase & gapi's most method handle the AuthorizationCode flow internally. They even further refresh the AccessToken after 60 minutes. This is beneficial for most developers as they won't have a headache of managing all the tokens.
This method but, hides RefreshToken & AuthorizationCode from the developer. That is even if your server has the access token, it won't be able to refresh it and it would be deemed useless.
To achieve complete offline access, in the initial request to get AuthorizationCode you will need to send a HTTP GET parameter access_type to offline
GAPI provides you with grantOfflineAccess() method which returns the AuthorizationCode that can be later used on your server to fetch access token & refresh token.
Note: If you are storing AuthorizationCode in your database, make sure it is secure. The limitation in Firebase are set due to security reason. It is more secure to not talk with AuthorizationCode generally.
More links
https://developers.google.com/identity/protocols/oauth2/web-server
https://developers.google.com/identity/sign-in/web/reference
https://developers.google.com/identity/sign-in/web/server-side-flow
https://developers.google.com/identity/sign-in/web/backend-auth
Retrieve Google Access Token after authenticated using Firebase Authentication

Got JWT Token from Azure AD B2C but getting 401 error in ASP.NET Core app

I'm implementing Azure AD B2C in a new ASP.NET Core 2.1 app.
I've already created the Azure AD B2C tenant and registered my app, etc.
After I login, I get redirected to the URL I specify and I see the token in the URL but I get an error stating the app requires authentication -- see below:
I saw a few similar posts and what I gather is that the token is automatically validated by the middleware. Is that not so?
What do I need to do at this point?
The code I included in my app are as follows:
In ConfigureServices() method:
services.AddAuthentication(options => {
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwtOptions => {
jwtOptions.Authority = $"https://login.microsoftonline.com/tfp/{Configuration["AzureAdB2C:Tenant"]}/{Configuration["AzureAdB2C:Policy"]}/v2.0/";
jwtOptions.Audience = Configuration["AzureAdB2C:ClientId"];
jwtOptions.Events = new JwtBearerEvents
{
OnAuthenticationFailed = AuthenticationFailed
};
});
In Configure() method in Startup.cs, all I have is app.UseAuthentication();
In my controller, I'm also using [Authorize] to make sure my actions are not open to anonymous users.
What am I missing? How do validate the token and get the claims?
Combining our discussion here as an answer.
The typical approach to this kind of app is that you allow unauthenticated clients to download the HTML, JS, and other static content.
Then the front-end can use MSAL.JS to authenticate the user.
The front-end SPA will get an Id token which tells the front-end who the user is.
MSAL.JS also allows you to get access tokens to call APIs.
It uses hidden iframes + the Implicit Grant flow to do this.
That access token will then need to be attached to requests to the API as a header (Authorization: Bearer token-goes-here).
MSAL.JS will use session or local storage to store the tokens (this is configurable).
So no cookies are used in this setup.
Then the back-end API should authenticate the access token it receives in the header.
What you have there is already sufficient to authenticate the token.
services.AddAuthentication(options => {
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwtOptions => {
jwtOptions.Authority = $"https://login.microsoftonline.com/tfp/{Configuration["AzureAdB2C:Tenant"]}/{Configuration["AzureAdB2C:Policy"]}/v2.0/";
jwtOptions.Audience = Configuration["AzureAdB2C:ClientId"];
jwtOptions.Events = new JwtBearerEvents
{
OnAuthenticationFailed = AuthenticationFailed
};
});
The JWT Bearer authentication handler will load the OpenId Connect metadata document on startup from the authority configured here.
That allows it to get the B2C tenant's public signing keys among other things.
This info allows the handler to then validate access tokens as they come in without interacting with B2C in any way.
It checks the signature is valid, the issuer is valid, and that the audience in the token is what has been configured.
Authorization is of course not handled by the authentication handler, so you must then also check that the calling user actually is allowed to access the resource they are accessing.
The user id is available in the access token.
MSAL.JS may have done some validation on the token as well (I can't remember right now if it did), but doing validation in the front-end is something that can be worked around by anyone with control of the user's browser.
Validation on the API side is the most important piece.

Azure AD authentication between applications

I have an application let's name it 'Apple' which is registered with Azure AD having delegated rights on Azure Management API application. When requested to this application it creates azure resource ex. storage account automatically and this works fine.
I have another application which is MVC application and it's also registered with same AD tenant. The second application uses following code for retrieving access token:
var clientCredentials = new ClientCredential(ConfigurationManager.AppSettings["AD_ClientID"], ConfigurationManager.AppSettings["AD_Client_AccessKey"]);
var authContext = new AuthenticationContext(string.Format(ConfigurationManager.AppSettings["AD_Tenant_Login_Url"], ConfigurationManager.AppSettings["AD_Tenant_Id"]));
var result = authContext.AcquireTokenAsync(ConfigurationManager.AppSettings["AD_Resource"], clientCredentials);
if (result == null)
{
throw new InvalidOperationException("Could not get the token");
}
return result.Result;
The result is an access token having different properties. Now second application, retrives access token with access to resource apple, which it then passes to Apple application in authorization header.
Authorization:bearer TokenString
The Apple application is having Authorize attribute added to controller.
The application is configured with Owin with oauth application with following code
public void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
},
});
}
Please note that the access token is retrieved from second application using its own AppId and Secret key; whereas the other(Apple) application uses its own AppId and secret key for validating the token.
So my problem is, the APPLE application always returns 401 not authorize code
To above, question, the answer was, Resource ID (during token request) and Audience Id (during validation of token in second application) were not matching. Keeping those same solved the problem.
Then I ran into another issue, which I have described here
It seems, If I work with newer Azure Portal (which is still in preview version), the AD token does not include "Roles" field in JWT token. If I follow same procedure in Older Portal for configuring apps, then AD includes "Roles" field in JWT token and scenario executes as expected.
I should avoid using Azure new portal for preview features at least!

Resources