How to authenticate on behalf of user to access their google calendar information from webjob/azure function? - asp.net-core

We have a webapp that will allow users to sync certain events in their calendar to our system. We can have the user login to google and authorize us to read their calendar and profile.
Now in order to sync their events we want to have an azure function, webjob running at certain intervals that connects to their google calendar and based on some logic will add the event information to our db so that the user can view content in our web app.
How can I get the azure function/webjob to authenticate on the users behalf after they have enabled the featured and authorized out app to get access to their calendar?
=======================================
UPDATE:
This is the code snippet example if anyone else needs it.
TokenResponse token = new TokenResponse { RefreshToken = syncUser.GoogleRefreshToken };
UserCredential credentials = new UserCredential(new GoogleAuthorizationCodeFlow(
new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "ClientIDValue",
ClientSecret = "ClientSecretValue"
}
}), "user", token);
CalendarListResource.ListRequest request = service.CalendarList.List();
CalendarList calendars = request.Execute();

Assuming you are storing the users refresh tokens and you can have your azure function read from where ever it is you are storing the refresh token. You just need to use the refresh token to request a new access token for the user and you can access their data offline.
Now I am not an Azure expert, but your going to have to ensure that you set up your redirect uri properly so that azure can get the response back from the authorization. You should also set your code to handle the refresh token expiring.
Also GoogleAuthorizationCodeFlow.Initializer supports IDataStore which means you could set up your own implementation of Idatastore and use that to feed it the refresh token from where ever it is you are grabbing it from.

Related

What's the best way of web api authentication to Microsoft Graph to access emails in the service account mailbox?

I am trying to figure out which way would be better for my application. I need to perform an automatic email import triggered from ASP.NET Core Web API using Microsoft Graph mailbox access. According to the documentation, there are two options to go for:
Get access on behalf of the user (https://learn.microsoft.com/en-us/graph/auth-v2-user?view=graph-rest-1.0)
Get access without the user (https://learn.microsoft.com/en-us/graph/auth-v2-service?view=graph-rest-1.0)
As the import is automatically triggered by Azure Function timer I do not want to open a popup for the user credentials. So I considered to go with the second option and create a service user to do this, but then I saw a point about Admin consent in the documentation and I got a bit confused. Does this mean that if nobody is going to accept the app rights to access emails it will not be able to do so? What would be easier/preferred way to implement this kind of functionality?
--Does this mean that if nobody is going to accept the app rights to access emails it will not be able to do so?
no, that means after admin consent the api permission, then you can access any users' emails in your tenant.
--What would be easier/preferred way to implement this kind of functionality?
as you said that you are using Azure timer function to to auto import the mails, so you shouldn't get access to graph api on behalf of a user, you have to access the api on behalf the application.
But you have to check if the graph api you want to call support being accessed on behalf of the application. Let's see this api. You may notice that it supports Application api permission.
Then pls note that you have to using client credential flow to generate access token to call the graph api, you can't use other flows. Here's sample code for using client credential flow with graph SDK to call graph api.
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "tenant_name.onmicrosoft.com";
var clientId = "azure_ad_appid";
var clientSecret = "client_secret";
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
var inboxMessages = await graphClient.Users["user_id"]
.MailFolders["inbox"]
.Messages.Request().GetAsync();

How to do 3-legged OAuth login with One Tap sign-in on Android

I'm trying to implement 3-legged OAuth login with Google's One Tap sign-in for Android. This is needed to access Gmail from the server side.
The app needs to get a server auth code and pass it to the server which stores it. The server will later exchange it for a refresh token and access token and use it to pull email.
I use the following code (error checking removed for brevity) which gets me a token, but it's NOT a refresh token. What I need is a server auth code that I can exchange for access and refresh tokens like.
// Show the one tap sign in account selector
List<String> scopes = new ArrayList<>(List.of("https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/userinfo.email",
..., ... // other scopes come here
));
BeginSignInRequest signInRequest = BeginSignInRequest.builder()
.setGoogleIdTokenRequestOptions(BeginSignInRequest.GoogleIdTokenRequestOptions.builder()
.associateLinkedAccounts(LINKED_SERVICE, scopes) // what is this LINKED_SERVICE?
.setSupported(true)
.setServerClientId(SERVER_CLIENT_ID)
.setFilterByAuthorizedAccounts(false)
.build())
.setAutoSelectEnabled(false)
.build();
SignInClient oneTapClient = Identity.getSignInClient(activity);
oneTapClient.beginSignIn(signInRequest).addOnCompleteListener(new OnCompleteListener<BeginSignInResult>() {
#Override
public void onComplete(#NonNull Task<BeginSignInResult> task) {
if (!task.isSuccessful())
return;
BeginSignInResult signInResult = task.getResult();
PendingIntent signInIntent = signInResult.getPendingIntent();
activity.startIntentSenderForResult(signInIntent.getIntentSender(), REQ_SIGN_IN, null, 0, 0, 0));
}
});
Then in the intent receiver:
// Handle response from the one tap account selector
public void onResult(Activity activity, int requestCode, int resultCode, Intent data) {
// Assume requestCode==REQ_SIGN_IN and resultCode is OK (for brevity)
SignInCredential cred = oneTapClient.getSignInCredentialFromIntent(data);
String idTokenString = cred.getGoogleIdToken();
// Now what? This is an Oauth token for client-side use.
// How to I get a server auth code that can be exchanged for a refresh and access tokens at the server-side?
}
Is there a way to get the server auth code?
Comment:
Until now I was using the GoogleSignIn API in the following manner, but it's being phased out and not sure when it's going to stop working (documentation states March 31, 2023).
// Start the OAuth sign in
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestScopes(new Scope("https://www.googleapis.com/auth/userinfo.profile"), new Scope("https://www.googleapis.com/auth/userinfo.email"))
.requestServerAuthCode(SERVER_CLIENT_ID)
.requestEmail()
.build();
Intent intent = GoogleSignIn.getClient(activity, gso).getSignInIntent();
activity.startActivityForResult(intent, REQ_SIGN_IN);
The result is returned to the activity (again, no error checking)
public void onResult(Activity activity, int requestCode, int resultCode, Intent data) {
Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
GoogleSignInAccount acct = task.getResult();
String authCode = acct.getServerAuthCode(); // Server will exchange this for access and refresh tokens
// Pass this authCode to the server
}
New Google Sign-In API using Identity.getSignInClient(activity) doesn't support 3-legged OAuth.
The documentation states:
For new apps we recommend using Google Identity Services instead of the
Google Sign-In API for sign-in and sign-up, unless you need
authorization, Server-Side Access (!!!), or custom OAuth scopes.
Old Google Sign-in is scheduled to be deprecated at March 31, 2023. However, until now (September 2022) there is no sign that the new API will add support.
Linked account sign-in uses access and refresh tokens issued by another, non-Google partner site or platform. Data is shared from the partner platform TO Google using OAuth2. In this scenario the partner platform defines the scope names, and Google requests and requests and manages the access and refresh tokens issued by the partner platform -- your app does not manage the
credentials. An example of this might be playing music or video managed by a different company on a Google Home hub.
From your comments it sounds like you are looking to request Google scopes and work with Google Account user data? In your example code, you are using authentication scopes only (email, profile) and simply using One Tap to obtain an ID token would be the most straightforward. Should you need to work with Google scopes, for Android you'll currently need to use the older Google Sign-in APIs. Somewhat confusingly, the sign-in options for user authentication are using newer Android API's while authorization to work with Google Account user data remains on the older Google Sign-in APIs.

How to receive the current user with create react app -auth template on net core 3 and identity server

I have never used identity server before and I was wondering how to receive the current user info if they have been authenticated using net core 3.
Using the barebones create react app -auth weather app template
in the WeatherForecastController we have the authorize attribute:
[Authorize]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
With the following endpoint:
[HttpGet]
public IEnumerable<WeatherForecast> Get()
Which will return the correct data if you have logged in and been granted a jwt bearer token.
I would like to create an endpoint that:
1. Allows anonymous access(logged in or not so the authorize attribute needs ditching or overriding)
2. It should then try to authenticate the user
3.If the user can be identified then the endpoint will return the correct data else if the user cannot be identified it will return a subset of the data
I'm struggling to retrieve the user who has made the request information, I believe i'm supposed to use GET /connect/userinfo identityserver endpoint with the bearer token as a parameter but I have no idea how to do this. I would like to access the user_id so I can corrispond with the identityUser table.
I have also tried:
var identity = (ClaimsIdentity)User.Identity;
IEnumerable<Claim> claims = identity.Claims;
It just feels like theres a proper way to do this stuff that i'm missing. I'd be grateful for someone to point me in the right direction.
It should then try to authenticate the user
Browsers communicate with web applications. You should have a web application to be able to redirect back to IDS4 to login. What you are have is an API. Later Web applications communicate with web APIs. Read more here
If the user can be identified then the endpoint will return the correct data else if the user cannot be identified it will return a subset of the data
What you are looking for is Authorization, you can manually do this check on the endpoint. You can access current user using ControllerBase.User ass a result of passing a valid JWT tokens to the API. Considering you already setup authentication using code like bellow:
services.AddAuthentication("Bearer").AddJwtBearer("Bearer",
options =>
{
options.Authority = "http://localhost:5000";
options.Audience = "api1";
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters()
{
NameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"//To set Identity.Name
};
});
I'm struggling to retrieve the user who has made the request information
You can simply use ControllerBase.User to get the current authenticated user on API, no need to call any endpoint.
Update: Setting NameClaimType as posted on code above will set Identity.Name to the user's identifier.

Identityserver4 Revoke One or All tokens

In the application, when a user logs out, I would revoke the reference and refresh token for that current session. This is an API and thus other devices can be logged into the application. I want to provide the ability to log out or log out from all devices.
In Identityserver4, when we are logging out, we can use the revocation client to revoke a token:
var client = new TokenRevocationClient(...);
//var result = await client.RevokeAccessTokenAsync(token);
//var result = await client.RevokeRefreshTokenAsync(token);
This, in turn, calls IReferenceTokenStore.RemoveReferenceTokenAsync(string handle).
Now, the question is, how would I revoke all tokens for a user? This means that it should call IReferenceTokenStore.RemoveReferenceTokensAsync(string subjectId, string clientId) instead.
You can inject an instance of IIdentityServerInteractionService and call RevokeTokensForCurrentSessionAsync().
This should lead to calling of IReferenceTokenStore.RemoveReferenceTokensAsync(string subjectId, string clientId).

OAuth Implicit flow Access Token expires every hour

I'm having a problem with the OAuth Implicit flow for the Google Assistant.
I managed to set up a OAuth server and got it to work. Here's the flow:
The user Is redirected to my endpoint, authenticates with a Google account, and gets send back to the Assistant with an Acces Token and result code=SUCCES.
In my fullfilment I get the users email address by doing a https request to: https://www.googleapis.com/plus/v1/people/me?access_token=access_token.
I then find the matching user in my database and add the acces token to the database for this user.
The next time the user logs in I check the acces token and greet the user by their name.
Now the problem is that this is the Implict flow which according to the documentation should have an access token that never expires:
Note: Google requires that access tokens issued using the implicit
flow never expire, so you don't need to record the grant time of an
access token, as you would with other OAuth 2.0 flows.
But the Assistant forces me to re-authenticate every hour, meaning the access token did expire.
My question is: Is this flow correct or am I missing something? Is there something I've done wrong in my OAuth endpoint?
I based my endpoint on https://developers.google.com/identity/protocols/OAuth2UserAgent.
<html>
<head>
<script src="https://apis.google.com/js/platform.js" async defer></script>
<meta name="google-signin-client_id" content="CLIENT_ID">
</head>
<body>
<script>
var YOUR_CLIENT_ID = 'CLIENT_ID';
function oauth2SignIn() {
// Google's OAuth 2.0 endpoint for requesting an access token
var oauth2Endpoint = 'https://accounts.google.com/o/oauth2/v2/auth';
// Create element to open OAuth 2.0 endpoint in new window.
var form = document.createElement('form');
form.setAttribute('method', 'GET'); // Send as a GET request.
form.setAttribute('action', oauth2Endpoint);
//Get the state and redirect_uri parameters from the request
var searchParams = new URLSearchParams(window.location.search);
var state = searchParams.get("state");
var redirect_uri = searchParams.get("redirect_uri");
//var client_id = searchParams.get("client_id");
// Parameters to pass to OAuth 2.0 endpoint.
var params = {
'client_id': YOUR_CLIENT_ID,
'redirect_uri': redirect_uri,
'scope': 'email',
'state': state,
'response_type': 'token',
'include_granted_scopes': 'true'
};
// Add form parameters as hidden input values.
for (var p in params) {
var input = document.createElement('input');
input.setAttribute('type', 'hidden');
input.setAttribute('name', p);
input.setAttribute('value', params[p]);
form.appendChild(input);
}
// Add form to page and submit it to open the OAuth 2.0 endpoint.
document.body.appendChild(form);
form.submit();
}
oauth2SignIn();
</script>
</body>
</html>
It sounds like what you are doing is having the user log into your page, and using this to get an auth token from a Google service. You're then turning this around and passing this back to the Assistant and calling this the Identity Flow.
While clever - this isn't the Identity Flow.
This is you using the Auth Code Flow to authenticate the user with Google, and then returning this token to Google and pretending this is an Identity Flow token. However, since you're using the Auth Code Flow, the auth tokens that you get back expire after an hour. (You can check out the lifetime in the information you get back from Google.)
If you are trying to do Account Linking and not manage anything yourself, you need to actually implement an OAuth server that proxies the Auth Code Flow requests from the Assistant to Google and the replies from Google back to the Assistant. While doable, this may be in violation of their policy, and isn't generally advised anyway.
Update to address some questions/issues in your comment.
using the Google Auth endpoints doesn't store the session either, so you'd still have to re-authenticate every hour
Since the Google Auth endpoints use the Auth Code Flow, you can use the offline mode to request a refresh token. Then, when an auth token expires, you can use the refresh token to get a new auth token. So you still have a long-term authorization for access and can get the short-term token to do the work you need.
Trying to shoehorn this into the Identity Flow, however, doesn't work. (And would be a really bad idea, even if it did.)
Can you provide some clarification on how to create an endpoint for the implicit flow?
Beyond the step-by-step description of what your OAuth server code can do in the Assistant documentation, I'm not sure what clarification you need. Your OAuth server fundamentally just needs to:
Be able to have a user:
Connect to an HTTPS URL
Authenticate themselves
Authorize the Assistant to contact your service on their behalf
Return a code by redirecting the user to Google's URL with a code in the parameter
And the Action webhook needs to be able to:
Accept this code as part of the request from the Assistant and
Figure out who the user is from this code. (ie - map the code to a user account in your system.)
There are a variety of ways you can do all of that. The OAuth server and Action could be on the same server or separate, but they at least need to have some agreement about what that code is and how that maps to your user accounts.
If your primary need is to access Google APIs on behalf of your user - then the user account that you have will likely store the OAuth tokens that you use to access Google's server. But you should logically think of that as separate from the code that the Assistant uses to access your server.
(As an aside - those steps are for the Identity Flow. The Auth Code Flow has a few more steps, but the fundamentals are similar. Especially on the Action side.)