I am starting out with MS Graph API. I need a login example with least amount of user intervention; if possible at most only once and the first time app is run. The sample GraphTutorial app (https://github.com/microsoftgraph/msgraph-training-dotnet-core/tree/main/demo) seems to require user intervention every time it is run with a code required to be manually entered.
Thanks
Regards
To call the graph api without login, it's possible with client credential flow. Microsoft also provides sample code for it. I also tested and pick some of the key code like below:
using Microsoft.AspNetCore.Mvc;
using System;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
namespace WebApplication4clientflow.Controllers
{
public class HelloController : Controller
{
public async Task<string> Index()
{
IConfidentialClientApplication app;
app = ConfidentialClientApplicationBuilder.Create("azure_ad_app_clientid")
.WithClientSecret("client_secret_of_your_app")
.WithAuthority(new Uri("https://login.microsoftonline.com/tenant_name.onmicrosoft.com"))
.Build();
AuthenticationResult result = null;
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
result = await app.AcquireTokenForClient(scopes)
.ExecuteAsync();
return result.AccessToken;
}
}
}
With the code above, we can generate access token to call graph api, but pls note, it's not suitable for those apis that don't support application permission, like below:
Pls feel free to let me know if you met further problems.
Related
I'm trying to put two things working at the same time and I have no luck.
In my .Net 6 Blazor WebAssembly Hosted, I can log to Azure AD accounts and it works fine following the sample:
https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/hosted-with-azure-active-directory?view=aspnetcore-6.0
Also, I can log to Microsoft Graph following this:
https://github.com/microsoftgraph/msgraph-training-blazor-clientside
But what I want is to be able to have a token valid for both. I want to call to Microsoft Graph and to call to my API from the server side.
Any idea how to mix both "samples" to make it work? I think the only I need is to "mix" in the program.cs this:
builder.Services.AddHttpClient("ReservasSalasAuth.ServerAPI", client =>
client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("ReservasSalasAuth.ServerAPI"));
And this:
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri("https://graph.microsoft.com") });
But I have no luck...
After some more investigation...I realize that the order in the AddMsalAuthentication makes the difference...
builder.Services.AddMsalAuthentication<RemoteAuthenticationState, CustomUserAccount>(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
var ApiScope = builder.Configuration.GetValue<string>("ApiScope");
options.ProviderOptions.DefaultAccessTokenScopes.Add(ApiScope);
options.UserOptions.RoleClaim = "appRole";
var scopes = builder.Configuration.GetValue<string>("GraphScopes");
foreach (var scope in scopes.Split(';'))
{
options.ProviderOptions.DefaultAccessTokenScopes.Add(scope);
}
}).AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, CustomUserAccount, GraphUserAccountFactory>();
This way, I take the Scope for the Api an it works the Api call but not the Graph call.
builder.Services.AddMsalAuthentication<RemoteAuthenticationState, CustomUserAccount>(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.UserOptions.RoleClaim = "appRole";
var scopes = builder.Configuration.GetValue<string>("GraphScopes");
foreach (var scope in scopes.Split(';'))
{
options.ProviderOptions.DefaultAccessTokenScopes.Add(scope);
}
var ApiScope = builder.Configuration.GetValue<string>("ApiScope");
options.ProviderOptions.DefaultAccessTokenScopes.Add(ApiScope);
}).AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, CustomUserAccount, GraphUserAccountFactory>();
And changing the order I put the scopes, it works the Graph call but not the API call.
Any ideas to make it work both two?
I had this same issue. You CAN'T use Msal to operate on two different authority.
So if you want to use graph and your api in the same time you need to chose One to use Msal with and for the other one you need to make the entire requirement yourself. So ask for authorize code, use it to get new access token and then use this new one for your second Http client as bearer in the header.
So yes you cannot achieve what you want with only one login.
Here look at last comment from Allen Wu
https://stackoverflow.com/a/65694725
Earlier our client side apps used Google Sign-In.
Now we are moving to custom auth, as we plan on having the user's phone number as the only identity (instead of a Google Account). But after implementing the custom Authenticator, the client IDs are not being checked and I am able to make API calls from anywhere.
When only Google Sign-in was being used at the client side, the client ID was being validated and I was not able to make API calls from any clients other than the ones authorized.
How do I verify the Client IDs while using custom authenticator?
Code for the Api Endpoint
#Api(name = "apiSubscriber",
clientIds = {
Constants.webClientId,
Constants.androidClientId,
Constants.iOSClientId
},
authenticators = {com.google.api.server.spi.auth.EndpointsAuthenticator.class,
CustomAuth.class},
audiences = {Constants.androidAudience},
)
public class ApiSubscriber {
#ApiMethod
public Subscriber getSubscriberData(User user){
if(user!=null){
//fetches subscriber data
}
}
//... Other ApiMethods
}
Code for Custom Authenticator
public class CustomAuth implements Authenticator {
#Override
public User authenticate(HttpServletRequest request) {
String phoneNumber = request.getHeader("phoneNumber");
String token = request.getHeader("Authorization");
if(checkToken(phoneNumber,token)){
return new User(phoneNumber);
}
return null;
}
private boolean checkToken(String phoneNumber, String token){
//Checks if authorization token is valid
}
}
Unfortunately at this time, it does not appear that you can restrict your Endpoints API to a client and not use Google Sign in.
When using Google's oAuth2 authentication some magic voodoo happens (not exactly sure what) and apps get restricted to the ClientId's that you specify.
However, when you stop using that authentication method, I have found (to my dear disappointment), that it does not work anymore.
See my question here where you can read about my tests and some additional things that may give you more information: Authenticating your client to Cloud Endpoints without a Google Account login
I don't sure is it a problem, but you have some bugs in code you provided.
authenticators = {com.google.api.server.spi.auth.EndpointsAuthenticator.class,
CustomAuth.class,
instead of comma must be bracket. Also, imho, you need only CustomAuth class here.
audiences = {Constants.androidAudience},
comma is redundant.
Second. You don't required to use custom Authenticator. You can send token and phone number as concatenated parameter or two parameters to your service method and check it there.
I am using Microsoft.Azure.ActiveDirectory.GraphClient version 2.1.1.0 to get groups that my user belongs to.
Method call is like this:
ActiveDirectoryClient activeDirectoryClient = new ActiveDirectoryClient(
new Uri(GraphUrl),
async () => await GetAppTokenAsync());
IEnumerable<string> groups = GetGroupsAsync(activeDirectoryClient, "currentUserObjectId").Result;
private static async Task<IEnumerable<string>> GetGroupsAsync(ActiveDirectoryClient activeDirectoryClient, string currentUserObjectId )
{
return await activeDirectoryClient.Users.GetByObjectId(currentUserObjectId).GetMemberGroupsAsync(true);
}
private static async Task<string> GetAppTokenAsync()
{
var authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext(ServiceRoot);
var token = await authContext.AcquireTokenAsync(GraphUrl,new ClientCredential("clientId", "clientSecret"));
return token.AccessToken;
}
However the method hangs even though in Fiddler I see that the request has succedeed and contains correct groups.
My question is duplicate of Azure ActiveDirectory Graph API GraphClient not returning AD Groups. A workaround exists but not a explanation why the method does not work.
If indeed your ServiceRoot value is the same for your instantiation of ActiveDirectoryClient and for your call to AuthenticationContext, that could be the source of your problem.
ActiveDirectoryClient should be instantiated with https://graph.windows.net/
AuthenticationContext should be called with
https://login.microsoftonline.com/
Though that wouldn't manifest itself in the method hanging nor a successful request, that was the only change I had to make to your code for it to work for me, otherwise it would return with a Not Found error.
I've had similar issues with the Graph Api library when using the Result property, try changing your call to this:-
IEnumerable<string> groups = await GetGroupsAsync(activeDirectoryClient, "currentUserObjectId");
Following is a code to get google contacts.
It was working fine but since few days I m getting exception of "Authentication request returned unexpected result: 404".
using Google.GData.Client;
using Google.Contacts;
using Google.GData.Extensions;
private void FetchContactList()
{
List<string> lstContacts = new List<string>();
RequestSettings rsLoginInfo = new RequestSettings("my application", "abc#gmail.com", "XXXXXX");
rsLoginInfo.AutoPaging = true;
ContactsRequest cRequest = new ContactsRequest(rsLoginInfo);
Feed<contact> feedContacts = cRequest.GetContacts();
foreach (Contact gmailAddresses in feedContacts.Entries)
{
// Looping to read email addresses
foreach (EMail emailId in gmailAddresses.Emails)
{
lstContacts.Add(emailId.Address);
}
}
GridView1.DataSource = lstContacts;
GridView1.DataBind();
}
Is google change something from their side?
Please suggest me way to solve the problem.
Update your current api with google api to version 3 and then make changes to the code according. Probably this may be the reason for the error.
https://developers.google.com/analytics/devguides/reporting/core/v3/gdataLibraries
i further suggest you use oauth2.0 for authentication as per the current requirements of your application and if you are using the older version of the api then you must use oauth2.0
Here is the link for oauth2.0 support: https://developers.google.com/google-apps/spreadsheets/authorize
though this is basically for spreadsheets but you can see how it is and working and change it to the requirements of your gmail api
I am building a REST API for a DotNetNuke 6 website, making use of DNN's MVC-based Services Framework. However, I don't have any background in authentication, so I'm not even sure where to start.
Basically, we want our clients to be able to make GET requests for their portal's data, and we want some clients (but not all) to be able to POST simple updates to their user data.
I've been trying to search for information, but the trouble is I'm not sure what I'm searching for. DNN has different logins and roles, but I'm not sure if or how they factor in. I've heard of things like oAuth but my understanding of it is at the most basic level. I don't know if it's what I need or not and if or how it applies to DNN. Can anyone point me in the right direction?
UPDATE:
Based on the answer below about tying it with a module and further research, here is what I have done:
I created a module just for this service, and I added two special permissions for it: "APIGET" and "APIPOST." I assigned these to some test roles/test accounts in DNN. I wrote a custom authorize attribute that, given the module ID, checks if the current user has the necessary permission (either through roles or directly). As far as I can tell, tab ID is irrelevant in my case.
It appears to be working both with a web browser (based on the DNN account I'm logged into) and with a php script that sends an HTTP request with an account username/password.
The authorize attribute:
using DotNetNuke.Entities.Modules;
using DotNetNuke.Entities.Portals;
using DotNetNuke.Security;
using DotNetNuke.Security.Permissions;
using System.Web;
public class MyAuthorize : DotNetNuke.Web.Services.AuthorizeAttributeBase
{
public const string AuthModuleFriendlyName = "MyAuthModule";
public const string GETPermission = "APIGET";
public const string POSTPermission = "APIPOST";
public string Permission { get; set; }
protected override bool AuthorizeCore(HttpContextBase context)
{
ModuleController mc = new ModuleController();
ModuleInfo mi = mc.GetModuleByDefinition(PortalController.GetCurrentPortalSettings().PortalId, AuthModuleFriendlyName);
ModulePermissionCollection permCollection = mi.ModulePermissions;
return ModulePermissionController.HasModulePermission(permCollection, Permission);
}
}
The controller:
("mytest" is the endpoint for both GET and POST)
public class MyController : DnnController
{
[ActionName("mytest")]
[AcceptVerbs(HttpVerbs.Get)]
[DnnAuthorize(AllowAnonymous = true)]
[MyAuthorize(Permission = MyAuthorize.GETPermission)]
public string myget(string id = "")
{
return "You have my permission to GET";
}
[ActionName("mytest")]
[AcceptVerbs(HttpVerbs.Post)]
[DnnAuthorize(AllowAnonymous = true)]
[MyAuthorize(Permission = MyAuthorize.POSTPermission)]
public string mypost(string id = "")
{
return "You have my permission to POST";
}
}
The main way that you tie a service in the DNN Services Framework into DNN permissions is to associate the permissions with a module instance. That is, you'll require users of your service to identify which module they're calling from/about (by sending ModuleId and TabId in the request [headers, query-string, cookies, form]), then you can indicate what permissions they need on that module to take a particular action on the service.
You can use the SupportedModules attribute on your service, and pass in a comma-delimited list of module names, to ensure that only your own modules are being allowed. Then, add the DnnModuleAuthorize attribute at the service or individual action level to say what permission the user needs on that module. In your instance, you can also add the AllowAnonymous attribute on the GET actions, and have one DnnModuleAuthorize on the service, for the POST methods (and anything else). Note that you cannot put the AllowAnonymous attribute on the controller; it will override authorizations put at the action, making it impossible to make actions more restrictive.
You'll also want to add the ValidateAntiForgeryToken attribute on the POST actions, to protect against CSRF attacks.
If you don't have a module that naturally associates its permissions with your service, you can create one just for that purpose, solely to expose itself as a permissions management utility.
Once you've figured out the authorization piece above, DNN will take care of authentication using your forms cookie (i.e. AJAX scenarios are taken care of automatically), or via basic or digest authentication (for non-AJAX scenarios). That said, if you're doing non-AJAX, you'll need to figure out a way to validate the anti-forgery token only when it applies.
The Services Framework in DNN is what you are after. It allows you to provide a REST API that plugs directly into DNN security.
Here are some articles to help you get started:
http://www.dotnetnuke.com/Resources/Wiki/Page/Services-Framework-WebAPI.aspx
http://www.dotnetnuke.com/Resources/Blogs/EntryId/3327/Getting-Started-with-DotNetNuke-Services-Framework.aspx
Note, there are some difference in DNN 6 and DNN 7 when using the Services Framework:
http://www.dotnetnuke.com/Resources/Blogs/EntryId/3514/Converting-Services-Framework-MVC-to-WebAPI.aspx
Just wanted to note that the DnnModuleAuthorize attribute takes a PermissionKey parameter for custom permissions so you can do stuff like this:
[DnnModuleAuthorize(PermissionKey = "DELETEDATA")]
[HttpPost]
public HttpResponseMessage DeleteData(FormDataCollection data)
It doesn't look like you can supply your own error message with this so you might to wrap your method body like this instead and leave off the custom permission attribute:
[DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.View)]
[HttpPost]
public HttpResponseMessage DeleteData(FormDataCollection data)
{
var errorMessage = "Could not delete data";
if (ModulePermissionController.HasModulePermission(ActiveModule.ModulePermissions,"DELETEDATA"))
{
// do stuff here
}
else
{
errorMessage = "User does not have delete permission";
}
var error = new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content =
new StringContent(
errorMessage)
};
return error;
}
Just wanted to add to #Richards comment for using the [DnnModuleAuthorize(PermissionKey = "DELETEDATA")] for custom permissions.
The full attribute should be:
[DnnModuleAuthorize(PermissionKey = "DELETEDATA", AccessLevel = SecurityAccessLevel.Edit)]
Leaving it blank does nothing as shown here: https://github.com/dnnsoftware/Dnn.Platform/blob/f4a5924c7cc8226cfe79bbc92357ec1a32165ada/DNN%20Platform/Library/Security/Permissions/PermissionProvider.cs#L810
I guess you require a plugin that allows you to construct GET and POST APIs. you can use this plugin I found on the DNN store. https://store.dnnsoftware.com/dnn-rest-api-custom-api-authentication-authorization.