How can I get the Google Role of the User?

91 Views Asked by At

I am setting up an web application on ASP .NET Core 5 with Google Auth. I want to be able to mark certain pages with an Authorize attribute to only allow users with a given Role to access. The access token sent by Google only contains name and email, not which group or role the user is assigned to. So I am trying to obtain the role with a call to the API, but how do I do that?

This is my Startup.cs

public class Startup
 {
     private IConfiguration Configuration { get; }

     public Startup(IConfiguration configuration)
     {
         Configuration = configuration;
     }

     public void ConfigureServices(IServiceCollection services)
     {
         services
             .AddAuthentication(options =>
             {
                 options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                 options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                 options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
             })
             .AddGoogleOpenIdConnect (async options =>
             {
                var googleAuthenticationSection = Configuration.GetSection("Authentication:Google");
                options.ClientId = googleAuthenticationSection["ClientId"];
                options.ClientSecret = googleAuthenticationSection["ClientSecret"];
                options.CallbackPath = new("/signin-oidc");
                options.Scope.Add("https://www.googleapis.com/auth/admin.directory.group.readonly");
                options.Scope.Add("https://www.googleapis.com/auth/admin.directory.rolemanagement.readonly");
                options.Scope.Add("https://www.googleapis.com/auth/admin.directory.user.readonly");
                options.ClaimActions.MapJsonKey(ClaimTypes.Role, "role", "string");
                options.Events = new OpenIdConnectEvents
                 {
                     OnMessageReceived = context =>
                     {
                         if (!string.IsNullOrEmpty(context.ProtocolMessage?.Error))
                         {
                             context.Response.Redirect($"/AccessDenied?error={context.ProtocolMessage.Error}&error_description={context.ProtocolMessage.ErrorDescription}");
                             context.HandleResponse();
                         }

                         return Task.CompletedTask;
                     }
                 };
// start of attempt to retrieve Role from Google Directory API
                 var credential = await GoogleAuthProvider.GetCredentialAsync();
                 var googleApiService = new DirectoryService(new BaseClientService.Initializer { HttpClientInitializer = credential });
                 var getRole = new RolesResource.GetRequest(googleApiService, "Zvi", "Owner");
                 var role = await getRole.ExecuteAsync();
// end of attempt to retrieve Role
             })
             .AddCookie();
       
         services.
             AddAuthorization(options =>
             {
               options.AddPolicy("WriteAccess", x => x.RequireClaim(ClaimTypes.Role, "Owner"));
             });

The problem is that GoogleAuthProvider is not injected into the Startup class so I cannot use it. I want to put it here so that I can insert a new claim with that value once it has been retrieved.

Of course, if anyone has a better way of getting the user's Role (or Group) without making an API call that would be better.

2

There are 2 best solutions below

2
Qing Guo On

Try to use OnUserInformationReceived. Invoked when user information is retrieved from the UserInfoEndpoint.

You can Gets or sets the user information payload from UserInformationReceivedContext.User. :

options.Events = new OpenIdConnectEvents
                {
                    OnUserInformationReceived = context =>
                    {
                        // If your authentication logic is based on users then add your logic here
                        return Task.CompletedTask;
                    }
}
0
Zvi On

I was looking in the wrong method. I had to catch the access_token and store it in the cookie.

 .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
 {
     options.Events = new CookieAuthenticationEvents
     {
         OnValidatePrincipal = context =>
         {
             var identity = (ClaimsIdentity)context.Principal.Identity;
             var refreshTokenClaim = identity.FindFirst("refresh_token");
             var accessTokenClaim = identity.FindFirst("access_token");
             var googleAuthenticationSection = Configuration.GetSection("Authentication:Google");
             var clientsSecrets = new ClientSecrets { ClientId = googleAuthenticationSection["ClientId"], ClientSecret = googleAuthenticationSection["ClientSecret"] };
             var scopes = new[] { DirectoryService.Scope.AdminDirectoryRolemanagementReadonly };
             return Task.CompletedTask;
         }
     };
 });

Once I had the access_token in the cookie I could use it to make API calls