How can I delete Azure B2C users directly from my app? - asp.net-core

I'm building an ASP .Net Core web App. I use Azure ADB2C for user authentication and I would like to have an admin user, which could delete other users. I can delete users from Azure Active Directory via Azure portal, but I would like to do it directly from the app. I have created an admin account in my Active Directory tenant, and gave it global administrator permissions.
I tried to use Graph API, but I can't get it to work. I created an IAuthenticationProved according to instructions on this website:
https://learn.microsoft.com/en-us/graph/sdks/choose-authentication-providers?tabs=CS.
Then I created GraphServiceClient and tried to delete user (https://learn.microsoft.com/en-us/graph/api/user-delete?view=graph-rest-1.0&tabs=csharp), but I got error:
System.AggregateException: 'Returning 0 accountsts and 0 broker accountsdata provider for login.microsoftonline.com. Success? True.)'
AuthenticationException: Code: authenticationChallengeRequired
Message: Authentication challange is required.
My code looks like this:
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create("<My Client ID>")
.WithRedirectUri("<My Redirect Uri>")
.WithClientSecret("<My Client secret>")
.Build();
List<string> scopes = new List<string>();
scopes.Add("https://graph.microsoft.com/User.ReadWrite.All");
scopes.Add("https://graph.microsoft.com/Directory.ReadWrite.All");
AuthorizationCodeProvider authProvider = new AuthorizationCodeProvider(confidentialClientApplication,scopes);
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
graphClient.Users[<UserId>]
.Request()
.DeleteAsync()
.Wait();
Could you tell me what I'm doing wrong? Or maybe there is some other way to do it? Thank you in advance!

Your approach to use Graph API is the way to go. But the problem you are facing there is because your selected auth provider (AuthorizationCodeProvider) is not matching with the scope needed here to delete user. Looking at your code, you want 'Authorization Code' flow which means it would need a delegated scope. As mentioned in permission requirement for delete user, the 'Delegated' scope needed to delete user is Directory.AccessAsUser.All which I don't see in the code snippet you shared.
Additionally, suggest you a quick read to Choose a Microsoft Graph authentication provider based on scenario.
NOTE: To add the required delegated permission, the application registration must be either of the first two types types shown below. The third option won't give you that.
For the above you will be able to add Directory.AccessAsUser.All delegated permission.

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();

Getting all messages from Microsoft Teams

I'm trying to get all the messages from Microsoft Teams in my tenant, I have registered the application to Azure, set the correct permissions and grated admin privileges.
What I am getting confused about is creating a GraphServiceClient.
My app is more of an Daemon Application.
I would really appreciate if someone could give me an example of how to create the client correctly.
this is my code so far:
string[] graphScopes = { "https://graph.microsoft.com/.default" };
IConfidentialClientApplication app = ConfidentialClientApplicationBuilder
.Create("x")
.WithTenantId("x")
.WithClientSecret("x")
.Build();
ClientCredentialProvider authProvider = new ClientCredentialProvider(app);
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
try
{
var messages = await graphClient.Teams["x"].Channels["x#thread.skype"].Messages.Request().GetAsync();
Console.ReadLine();
foreach(var item in messages)
{
Console.WriteLine(item.Body);
}
}
catch(Exception e)
{
Console.WriteLine(e.Message);
Console.Read();
}
I'm getting the following error no matter what I'm trying to get
Code: UnknownError Inner error: AdditionalData: request-id: x date: 2020-05-27T14:22:37 ClientRequestId:x
update: I was able to get something from the API, I had wrong permissions.
still can't get the messages though,
I have all these permission:
ChannelMessage.Read.All, Group.Read.All, Group.ReadWrite.All
I'm probably missing the "ChannelMessage.Read.Group (RSC)" permission but I can't find it in the permissions page.
May this is the solution or the problem ;-)
Microsoft Teams APIs in Microsoft Graph that access sensitive data are considered protected APIs. These APIs require that you have additional validation, beyond permissions and consent, before you can use them.
https://learn.microsoft.com/en-us/graph/teams-protected-apis
Your problem is you are accessing a "beta" api but using the production base url path.
The permission you need is ANY of the following (i.e. or not and):
ChannelMessage.Read.Group (RSC) OR
ChannelMessage.Read.All OR
Group.Read.All OR
Group.ReadWrite.All
Since you have Group.Read.All, that is ALL you need for permissions.
What you need to do is change the base URL to the beta api:
graphClient.BaseUrl = "https://graph.microsoft.com/beta";
UPDATED:
Since now you are saying that you are getting a "Forbidden" error, I think you also have a consent problem.
My guess is that you created & consented you app on one tenant but you are trying to access the data in another tenant. This will give you a forbidden errors. i.e. you created and consented on a dev azure account tenant and are trying to access your work tenant.
If this is the situation you need to:
* Make sure that the setup you azure app to be multi-tenanted
* You have to get your app consented by the target tenant
If you do that and use the beta endpoint I would expect that your example code will start working.
Update2:
Finally got around to trying to do the message list with a application context like you above and I get the same Forbidden error as well from the beta api. From a user context it works fine. So your answer will be to use a user context and not an application context to access this API.
It looks like what you are hitting is a Protected API. So if you want to use this API from an application context, you will have to submit a request to be allowed access to it.

Is there a way to know which aad to use for authenticating in advance?

I have a UWP app that needs to authenticate, and I would like to avoid asking the user to choose which national cloud to authenticate with. I could just try them all, but I hope there is a better way to tell which Azure Active Directory the user belongs to (.us or .com)
Native apps can discover the Azure AD endpoint for a national cloud by passing an instance_aware parameter in the authorization request to the global Azure AD endpoint. This is done in the acquireToken call, where you need to pass in instance_aware = true as an extra query parameter when initializing the Authentication context.
From the Authentication result, you can read and store the cloud_instance_host_name attribute to learn the correct Azure AD endpoint. You must pass this value as the authority to re-initialize the Authentication context for the subsequent acquireTokenSilent calls to succeed.
An example ADAL.Net code snippet is below:
var authenticationContext= new AuthenticationContext(Authority, false, new TokenCache());
var authenticationResult = await authenticationContext.AcquireTokenAsync(resource,
clientId,
redirectUri,
platformParameters,
userIdentifier,
"instance_aware=true"
);
Also, here are example OAuth request and responses using the instance_aware parameter:
Request:
https://login.microsoftonline.com/common/oauth2/authorize?
response_type=code&
client_id=f5d01c1c-abe6-4207-ae2d-5bc9af251724&
instance_aware=true&
redirect_uri=http://localhost/appcheck&
resource=00000002-0000-0000-c000-000000000000
Response:
http://localhost/appcheck?
code=AQABAAIAAQDnLpu3ikefR73l_aNlxt5x0ulCIcjaTlOoWp412SJ2Oxlih65_h_Ju3OdOqpEy-mz0giFzZtU2_MbIgSG12e6RjwxpcaXaVPene_lMtmR2DPexUZZ3QhFRl8Vgl76SidX_nJ1CN-hJAejCi139FG_YZit4ePbiNySC3zR9GcP3B3St7HDsdEhMh1Vi1XHSSKfpgVqzLnOiBSO_jXrm1WJVqXSlt4_M_KO92Gdpbpy8H7zpsRg0O6blbuSw_83YUcj0w1gEfByHZP2Hk5AToDy_DWepPqJ0GWOJYeKcfIiEFleNYaeyEJDDuMyFhV16IOT28mq1oNOWL0dnhjwr-OV0JnyajQCT_LZzapxp7Y-8jSPDgW6SR878sgrq6CS2z3Zos8_T31n4DucQaPqv2Ae_jxlGHHSENBFy2RhHy397B7BBohXGqhDj_OdIroimDOJGVewn612gQOA6-9p0llv-PNd7vj9VZL-9Q8kEuYuhTqaBsH3yKm6y9FfgxMWovVkYtDt4YgxbqCV2Wb_lzImtyTHKxazn6YhH6R2pCvFdVSAA&
cloud_instance_name=microsoftonline.us&
cloud_instance_host_name=login.microsoftonline.us&
cloud_graph_host_name=graph.windows.net&
msgraph_host=graph.microsoft.com&
session_state=899b8a55-034f-4dcd-8b4b-888b7874b041
One way to "spot check" which cloud/AAD environment the user belongs to is by making a call to the Azure AD OpenID Connect discovery endpoint:
https://login.microsoftonline.com/place_tenantname_or_tenantid_here/.well-known/openid-configuration
For Azure Government, "USG" or "USGov" as the value in the tenant_region_scope field will indicate tenants that should be using login.microsoftonline.us.
I hope this helps. Let me know if not.
Bernie

Authenticate Google access token with ASP.NET Core backend server

I have Angular2 on client and ASP.NET Core on server side. I use JavaScriptServices (aspnetcore-spa template).
For authentication I use OpenIddict and I follow example here.
Now I am on the server side in Controller class method and I would like to validate id_token because this is suggested on this side:
Important: Do not use the Google IDs returned by getId() or the user's
profile information to communicate the currently signed in user to
your backend server. Instead, send ID tokens, which can be securely
validated on the server.
And I would also like to register user (save email, profile ...) in my database through ASP.NET Core identity.
I would like to use Google API client Library for .NET to get user information and store refresh_token. Years ago I manage to do it with PHP, but I can't figure it out with .NET.
I download nuget packages: Google.Apis, Google.Apis.OAuth2.v2, Google.Apis.Plus.v1.
I am not sure which nuget package I need for this, which class should I use, how to set Google ServerKey and how to get user information from information which I get from gapi.signin2 button.
In simple:
How can I validate id_token from .NET with Google .NET Client library?
I found solution here. It is old, but it works.
var googleInitializer = new BaseClientService.Initializer();
googleInitializer.ApiKey = this.config["Authentication:Google:ServerKey"];
Oauth2Service ser = new Oauth2Service(googleInitializer);
Oauth2Service.TokeninfoRequest req = ser.Tokeninfo();
req.AccessToken = request.AccessToken; //access token received from Google SignIn button
Tokeninfo userinfo = await req.ExecuteAsync();
I didn't figure it out how to get Display name and picture on server. But it can be done on client:
onGoogleLoginSuccess(user: gapi.auth2.GoogleUser)
{
console.log("basic profile", user.getBasicProfile());
}
If someone knows more updated solution or how to retrieve basic user profile on server, please share it.
In addition I can use Google+, but careful because Google Account is not Google+ Account. I didn't have + account and get error:
Google.Apis.Requests.RequestError Not Found [404] Errors [
Message[Not Found] Location[ - ] Reason[notFound] Domain[global] ]
in code:
var plusService = new PlusService(googleInitializer);
Person me = await plusService.People.Get(userinfo.UserId).ExecuteAsync();
but it is possible to get all user information (picture, display name, first name, last name, birthday ...)

Role membership creation with ADFS login

I am creating an MVC4 application that uses ADFS SSO login. I am trying to configure Roles so that I can restrict the Admin portion of the content But I am having some problems. I am hoping someone will be able to tell me how I can go about creating Roles when using ADFS? I have the pages restricted successfully I just need now to be able to Add the actual role itself and I am hoping someone can help me out.
In my Global.asax file I have enabled Roles using the code:
System.Web.Security.Roles.Enabled = true;
And on my HomeControllor I tried using the following code:
if (hvm.User.isAdmin == true)
{
if(!User.IsInRole("Admin"))
Roles.AddUserToRole("jpmcfeely", "Admin");
}
This results in the following error:
This method can only be called during the application's pre-start initialization phase. Use PreApplicationStartMethodAttribute to declare a method that will be invoked in that phase.
Any suggestions would be greatly appreciated.
The easiest way is to configure ADFS to map AD groups to roles.
ADFS : Sending groups as claims.
e.g. map "Token-Groups - Unqualified Names" to Roles. Then IsInRole works OOTB.