How to implement OAuth in google classroom - google-oauth

Presently i am working on google classroom API to integrate classroom into my .NET product.I am using below method for authenticating user.My problem is when i execute this code it asking authentication for first time but when i execute this code next time it directly log in as previous log in credentials.When i try this after many days and many browsers also directly log in as first authenticated user.But for every fresh time execution of code i want it ask for authentication of user rather than directly log in as previous user credentials.How to achieve this...?
I am new to this OAuth and API's.Your valuable answer will help my team a lot.
please any one help me on this...
private ClassroomService getservice()
{
using (var stream =
new FileStream(Server.MapPath("client_secret1.json"), FileMode.Open, FileAccess.Read))
{
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
"user",
CancellationToken.None).Result;
}
var service = new ClassroomService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
return service;
}

Even if you don't pass in a data store object, the library by default will store the user's credentials in C:\Users\%USERNAME%\AppData\Roaming\Google.Apis.Auth\. If you don't want to store the auth information at all, and instead have the user authorize the application on each run, you'll need to pass in a custom data store object that doesn't actually store the credentials:
class NullDataStore : IDataStore
{
public Task StoreAsync<T>(string key, T value)
{
return Task.Delay(0);
}
public Task DeleteAsync<T>(string key)
{
return Task.Delay(0);
}
public Task<T> GetAsync<T>(string key)
{
return Task.FromResult(default(T));
}
public Task ClearAsync()
{
return Task.Delay(0);
}
}
Then pass an instance of this class into the AuthorizeAsync() method:
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
"user",
CancellationToken.None,
new NullDataStore()).Result;

Related

Why when requesting a token using the grant password and authorization code, I do not get the same claims?

I have a Identity Server client set up to be able to use the password and authorization code grants, I am able to use both, but when reviewing the tokens they do not contain the same claims, is this how its suppose to work or I am missing some configuration?
If this is how it works (different claims in each grant) when using the password grant should I use the Profile service to add the other claims?
You need to implement an IResourceOwnerPasswordValidator, and return the list of claims you need. The default implementation only sends the sub claim.
See the example implementation for ASP.NET Core Identity in IdentityServer repo.
Then modify it to send additional claims. Or use ProfileService to populate it:
public virtual async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
var user = await _userManager.FindByNameAsync(context.UserName);
if (user != null)
{
var result = await _signInManager.CheckPasswordSignInAsync(user, context.Password, true);
if (result.Succeeded)
{
var sub = await _userManager.GetUserIdAsync(user);
_logger.LogInformation("Credentials validated for username: {username}", context.UserName);
// return additional claims
var claims = await _userManager.GetClaimsAsync(user);
context.Result = new GrantValidationResult(sub, AuthenticationMethods.Password, claims);
return;
}
// ... see the link above for a full implementation
}
You can also create a new ClaimsPrincipal to populate the results. See the GrantValidationResult constructor overloads for other options.

Blazor Web Assembly App with Azure B2C is always trying to authenticate as soon as page is loaded

I am adding support for Azure AD B2C to a Blazor WebAssembly App, I followed the instructions here
https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/hosted-with-azure-active-directory-b2c?view=aspnetcore-3.1#client-app-configuration
however, the application is always trying to authenticate as soon as I load the page,
which does not allow for a public anonymous section of the site.
Is there any solution to this problem?
The default httpClient requires authorization so even making a call to see if a person is authorized causes the code to prompt the user to log in kicks in.
So to get around this, in the Program.cs file (in the Client project), I created a httpClient that allows anonymous requests
// This allows anonymous requests
// See: https://learn.microsoft.com/en-us/aspnet/core/security/blazor/webassembly/additional-scenarios?view=aspnetcore-3.1#unauthenticated-or-unauthorized-web-api-requests-in-an-app-with-a-secure-default-client
builder.Services.AddHttpClient("ServerAPI.NoAuthenticationClient", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));
This example should help:
https://github.com/ADefWebserver/SyncfusionHelpDeskClient/blob/main/Client/Pages/Index.razor
It calls the NoAuthenticationClient httpClient
protected override void OnInitialized()
{
// Create a httpClient to use for non-authenticated calls
NoAuthenticationClient =
ClientFactory.CreateClient(
"ServerAPI.NoAuthenticationClient");
}
public async Task HandleValidSubmit(EditContext context)
{
try
{
// Save the new Help Desk Ticket
// Create a new GUID for this Help Desk Ticket
objHelpDeskTicket.TicketGuid =
System.Guid.NewGuid().ToString();
await NoAuthenticationClient.PostAsJsonAsync(
"SyncfusionHelpDesk", objHelpDeskTicket);
// Send Email
HelpDeskEmail objHelpDeskEmail = new HelpDeskEmail();
objHelpDeskEmail.EmailType = "Help Desk Ticket Created";
objHelpDeskEmail.EmailAddress = "";
objHelpDeskEmail.TicketGuid = objHelpDeskTicket.TicketGuid;
await NoAuthenticationClient.PostAsJsonAsync(
"Email", objHelpDeskEmail);
// Clear the form
objHelpDeskTicket = new HelpDeskTicket();
// Show the Toast
ToastContent = "Saved!";
StateHasChanged();
await this.ToastObj.Show();
}
catch (Exception ex)
{
ToastContent = ex.Message;
StateHasChanged();
await this.ToastObj.Show();
}
}

How to validate if a user logged in with Google is still valid?

I'm running .NET Core v3.1 and Blazor and have implemented authorization using Google limited to our domain in Google G Suite as mentioned here: https://www.jerriepelser.com/blog/forcing-users-sign-in-gsuite-domain-account/
Login/logout is working fine, but when the user who logged in is blocked or removed in Google G Suite the user stays logged in into my application until he logs out from the application. When he doesn't logs out he can keep using the application.
I'm looking for a refresh every hour.
This is my login.cshtml.cs:
public async Task<IActionResult> OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
{
// Get the information about the user from the external login provider
var GoogleUser = User.Identities.FirstOrDefault();
if (GoogleUser.IsAuthenticated)
{
var authProperties = new AuthenticationProperties
{
IsPersistent = true,
RedirectUri = Request.Host.Value,
IssuedUtc = System.DateTime.UtcNow,
ExpiresUtc = System.DateTime.UtcNow.AddHours(1)
};
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(GoogleUser), authProperties);
}
return LocalRedirect("/");
}
I already added IssuedUtc and ExpiresUtc but that didn't change anything.
You have to enable the ability to call Google APIs (https://www.googleapis.com/auth/admin.directory.user, https://www.googleapis.com/auth/admin.directory.group) to get this information, but, before you can do that, the G-Suite Domain Admin has to authorize that access using https://developers.google.com/admin-sdk/directory/v1/guides/authorizing
This explains the process:
https://developers.google.com/admin-sdk/directory/v1/guides/delegation
You will want to see this GitHub repo for code samples:
https://github.com/googleapis/google-api-dotnet-client
Here is some psudo code:
string[] Scopes = {
DirectoryService.Scope.AdminDirectoryGroup,
DirectoryService.Scope.AdminDirectoryUser
};
GoogleCredential credential;
//redirectUrl = this.Request.Host.Value;
string keyfilepath = "yourKeyFile.json";
using (var stream = new FileStream(keyfilepath, FileMode.Open, FileAccess.Read))
{
// As we are using admin SDK, we need to still impersonate user who has admin access
// https://developers.google.com/admin-sdk/directory/v1/guides/delegation
credential = GoogleCredential.FromStream(stream)
.CreateScoped(Scopes).CreateWithUser(EmailOfGoogleDomainAdmin);
}
// Create Directory API service.
var service = new DirectoryService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "ApplicationName",
});
// G Suite User to get information about
// This test user should be suspended
var gs_email = UserToCHeck;
var request = service.Users.Get(gs_email);
var result = request.Execute();
Console.WriteLine("Full Name: {0}", result.Name.FullName);
Console.WriteLine("Email: {0}", result.PrimaryEmail);
Console.WriteLine("ID: {0}", result.Id);
Console.WriteLine("Is Admin: {0}", result.IsAdmin);
Console.WriteLine("Is Suspended: {0}", result.Suspended);

Verify if user account exists in Azure Active Directory

I need to send an email to users from an ASP.NET Core 2 application, following some business rules. However, I need to ensure that the account the email is being sent to actually exists (for some reason, it may be that the account stopped being valid). The customer is using Azure Active Directory, so I need to query AAD somehow so it lets me know whether the account exists or not.
So far I have been looking for Microsoft Graph as a way to do this, however every example I have seen so far requires prior authentication and use a delegate authentication mechanism. I don't want my users having to authenticate nor to prompt the authentication screen.
Given this situation, what would you recommend using? If you can also point me to an example, that would be great. Thanks!
You don't really need to throw/catch exception for every invalid user as you're doing in current code. I have nothing against exception handling in general for other reasons but to see if the user exists or not you can try using Filter.
So your graph query could look like -
https://graph.microsoft.com/v1.0/users?$filter=startswith(userPrincipalName,'someuser#mytenant.onmicrosoft.com')
I have shown startswith here becuase eq didn't work for me in a quick trial. Although I would recommend two things:
Go through Microsoft documentation on Filters here and see what works best for your requirements - Use query parameters to customize responses with Microsoft Graph
Play a little bit with different queries in Microsoft Graph Explorer it's very simple and easy to use.
Here is a modified version for your code.
Note that I'm checking for the collection count to be > 0 and not checking for it to be null, as even in case user is not found the UsersCollectionPage was not null for my test run.
using Microsoft.Identity.Client;
using Microsoft.Graph.Auth;
using Microsoft.Graph;
...
private async Task<bool> ValidateAccounts(string accounts) {
var confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create("clientId here")
.WithTenantId("tokenId here")
.WithClientSecret("secret here")
.Build();
var authProvider = new ClientCredentialProvider(confidentialClientApplication);
var graphClient = new GraphServiceClient(authProvider);
var valid = true;
try {
foreach (var account in accounts.Split(';')) {
var user = await graphClient.Users.Request().Filter("startswith(userPrincipalName, '" + account + "')").GetAsync();
if (user.Count <= 0) {
valid = false;
break;
}
}
} catch (ServiceException ex) {
valid = false;
}
return valid;
}
On a side note, I'm not not sure of your requirements but you could probably get creative by combining multiple user names in single query and then checking for result counts or other propertes. You could use or between multiple criteria or probably use any operator. I haven't really tried this out though.
Finally I came up with something workable. It's not nice, and it uses preview software. First, install Microsoft.Graph and Microsoft.Identity.Client packages. Then install Microsoft.Graph.Auth, which at the time of this writing, is in preview (v1.0.0-preview.1) so you'll need to tick "include prerelease" checkbox in nuget manager.
Then in your AAD, you need to get the ClientId, TenantId and SecretId. In my case, my app was already using AAD authentication so I already had ClientId and TenantId in my appsettings.json file. I only needed to create a new SecretId (in the Certificate & secrets section of my app registration). Then I needed to add permissions (in the API permissions section of my app registration) to include Microsoft.Graph with at least User.Read.All permission.
using Microsoft.Identity.Client;
using Microsoft.Graph.Auth;
using Microsoft.Graph;
...
private async Task<bool> ValidateAccounts(string accounts) {
var confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create("clientId here")
.WithTenantId("tokenId here")
.WithClientSecret("secret here")
.Build();
var authProvider = new ClientCredentialProvider(confidentialClientApplication);
var graphClient = new GraphServiceClient(authProvider);
var valid = true;
try {
foreach (var account in accounts.Split(';')) {
var user = await graphClient.Users[account]
.Request()
.GetAsync();
if (user == null) {
valid = false;
break;
}
}
} catch (ServiceException ex) {
valid = false;
}
return valid;
}
Here, the function takes a semicolon-separated string for each account. The GetAsync method will throw a ServiceException if the user does not exist. I don't like that, but couldn't find another way. So that's about it. Hope this helps someone else, and hope someone could come up with a better solution eventually.
Import following namespaces (You needs to install relevant packages using nuget):
using Microsoft.Graph;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
Set your Azure AD app values:
private string _tenant => "your_tenant_id";
private string _appId => "your_ad_app_client_id";
private string _appSecret => "your_app_client_secret";
Create Graph Service Client using this:
public static GraphServiceClient CreateGraphServiceClient()
{
var clientCredential = new ClientCredential(_appId, _appSecret);
var authenticationContext = new AuthenticationContext("https://login.microsoftonline.com/{_tenant}");
var authenticationResult = authenticationContext.AcquireTokenAsync("https://graph.microsoft.com", clientCredential).Result;
var delegateAuthProvider = new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", authenticationResult.AccessToken);
return Task.FromResult(0);
});
return new GraphServiceClient(delegateAuthProvider);
}
var graphServiceClient = GraphServiceClientHelper.CreateGraphServiceClient();
Then call graph api and filter users by Email Address as follows:
var user = await graphServiceClient.Users.Request().Filter("mail eq '" + UserEmailAddress + "'").GetAsync();
if (user.Count == 0) {
//user not exist
}
Below code worked for me.
using Microsoft.Graph;
using Microsoft.Graph.Auth;
using Microsoft.Identity.Client;
private static async Task<bool> ValidateAccounts(string accounts)
{
var confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create("client id")
.WithTenantId("tenant id")
.WithClientSecret("client secret")
.Build();
var authProvider = new ClientCredentialProvider(confidentialClientApplication);
var graphClient = new GraphServiceClient(authProvider);
var valid = true;
try
{
foreach (var account in accounts.Split(';'))
{
var user = await
graphClient.Users.Request().Filter($"identities/any(c:c/issuerAssignedId eq
'{account}' and c/issuer eq 'xyz.onmicrosoft.com')").GetAsync();
if (user.Count <= 0)
{
valid = false;
break;
}
}
}
catch(Exception ex)
{
valid = false;
}
return valid;
}

How can MonoTouch supply cookie on each ServiceStack request?

I've spent several days attempting to get to grips with ServiceStack and it seems great. Only issue is with authentication which seems to be a lot of friction, hard work and tears.
I want MonoTouch to register users, authenticate against ServiceStack/authenticate against OAuth and generally minimise calls to the database when authenticating.
So far I have got this:
var client = new JsonServiceClient(newbaseUri);
// register a new user:
var registration = new Registration {
FirstName = "john"
UserName = "user" ,
Password = "pass",
Email = "john#john.com",
};
var registerResponse = client.Send<RegistrationResponse>(registration);
--------------------------------
// user registered...later on I authenticate:
var authResponse = client.Send<AuthResponse>(new Auth {
UserName = "user",
Password = "pass",
RememberMe = true
});
var authResponse = clientlogin.Send<AuthResponse>(auth);
--------------------------------
// somehow I need to store 'authresponse' for later calls, not sure how without a browser
// tried manually setting the cookies and credentials parameters later but no joy
// but now I need to call a secured ([Authenticate] decorated) service method:
var client = new JsonServiceClient(newbaseUri);
var response = client.Send<HelloResponse>(new Hello { Name = "World!" });
return response.Result;
-----------------------------------------
// heres the configuration
var appSettings = new AppSettings();
//Default route: /auth/{provider}
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new CredentialsAuthProvider(appSettings), // never seems to get called
//new FacebookAuthProvider(appSettings), // not sure how to get this to work on monotouch
//new TwitterAuthProvider(appSettings), // same issue as facebook
new BasicAuthProvider(appSettings) // works but what about caching/tokens/cookies?
}));
//Default route: /register
Plugins.Add(new RegistrationFeature()); // how do i send extra params to this as created in mongodb collection
var mongoClient = new MongoClient("mongodb://localhost");
var server = mongoClient.GetServer();
var db = server.GetDatabase("users");
container.Register<ICacheClient>(new MemoryCacheClient());
container.Register<IUserAuthRepository>(new MongoDBAuthRepository(db, true));
My question is:
1) How do I enable extra fields to be passed in along with registration (as the mongodb [Servicestack.Authentication.Mongodb] has lots of empty fields i.e. birthdate, firstline, city, timezone, etc) that are not present in ServiceStack.Common.ServiceClient.Web.Registration object?
2) How can I transfer the cookie (or even maybe a token system) sent in the 'authresponse' to subsequent calls in order to allow ServiceStack to match against the session for ongoing authentication rather than more ongoing database calls that what seems to be issue with 'basic authentication' method (i.e CredentialsAuthProvider doesnt get called on server side)?
Please help...I've read documentation, run tests, examined social bootstrap and now I'm seriously losing days over this and thinking of integrating SS with simplemembership or even throwing ServiceStack away completely for old skool soap/wcf which is far easier to implement by the looks of it :(
1) If you want to use the Registration Plugin I don't think you can add additional fields since the Registration request/class is already defined. You could make your own registration Service and call into the RegistrationService/Plugin. Also, this post might be helpful.
[Route("/myregistration")]
public class MyRegistration : Registration //Add Additional fields for registration
{
public DateTime? BirthDate { get; set; }
public string Gender { get; set; }
}
public class MyRegisterService : Service
{
public IUserAuthRepository UserAuthRepo { get; set; }
public object Post(MyRegistration request)
{
using (var registrationService = base.ResolveService<RegistrationService>())
{
//handle the registration
var response = registrationService.Post(request.TranslateTo<Registration>());
}
//save the additional data
var userAuth = request.TranslateTo<UserAuth>();
UserAuthRepo.SaveUserAuth(userAuth);
//can make your own response or grab response from RegistrationService above
return new MyRegistrationResponse();
}
}
2) You can authenticate your JsonServiceClient and reuse it to make multiple requests.
var client = new JsonServiceClient(newbaseUri);
var authResponse = client.Send<AuthResponse>(new Auth {
UserName = "user",
Password = "pass",
RememberMe = true
}); //if successful your 'client' will have a populated CookieContainer with 'ss-id' and 'ss-pid' values
//reusing 'client' (after successful authentication) to make a request
//to a service requiring authentication
var response = client.Send<HelloResponse>(new Hello { Name = "World!" });
If reusing your 'client' is not an option you can try to store the ss-id. I don't know much about MonoTouch and how it stores 'browser sessions' so I'm not sure how you would accomplish this. After you authenticate and store the ss-id you can add it to the client using a Request Filter
//Get ss-id value
foreach(Cookie cookie in previousAuthenticatedClient.GetCookies(new Uri(newbaseUri)))
{
if (cookie.Name == "ss-id")
{
//store ss-id
}
}
var newClient = new JsonServiceClient(newbaseUri)
{
LocalHttpWebRequestFilter = (req) =>
{
req.CookieContainer.Add(new Uri("http://localhost:56006"), new System.Net.Cookie("ss-id", ssId));
}
};