Business Requirement
OneDrive to have a user level folder created and shared to specific members of team.
Owning user to create folders for project and assign exclusive permissions to certain members (remove access to non-project members)
Technical Environment
One Drive access via Graph API
ADAL Auth
Web Job hosted as NativeJob in Azure App and granted required OneDrive access.
App registered in mine (not the account used to access OneDrive)
Current Effort
Since web-api & web apps cannot use username/password auth, required to access a specific user's drive, resorted to WebJob (not sure if it complies as native app).
WebJob tries to auth using the drive owner's credential but fails with message (AADSTS65001: The user or administrator has not consented to use the application with ID '<appId>' named 'OneDriveFileSystem'. Send an interactive authorization request for this user and resource.)
Code
Auth Request
UserCredential uc = new UserPasswordCredential(userName, password);
AuthenticationResult result = null;
try
{
var authContext = new AuthenticationContext(authority);
result = authContext.AcquireTokenAsync(resource, clientId, uc).Result;
}
catch (Exception ee)
{
return string.Empty;
}
return result.AccessToken;
OneDriveAccess
string accessToken = GetAuthToken();
if (!string.IsNullOrEmpty(accessToken))
{
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
string payload = "{ \"name\": \"" + projectRequest.ProjectName + "\" , \"folder\": { } }";
HttpContent content = new StringContent(payload,
System.Text.Encoding.UTF8,
"application/json");
Uri uri = new Uri(resource + "/v1.0" + RootFolderItemId + RootFolderItemId + "/children");
HttpResponseMessage response = httpClient.PostAsync(uri, content).Result;
if (response.IsSuccessStatusCode)
{
var responseContent = response.Content;
}
}
(results in 404, however same url works in Graph Explorer)
Question
Is this design correct or am I missing some critical element?
Do I need to register the app in the account which is used for OneDrive access or can it be allowed to register in any user's account as long as the keys are correct?
If I update the code to use my credential, it asks for multi-factor auth (enabled by admin), but since this is native, this cannot be provided. The drive owner account was created as a service one, so MFA is not applicable to that.
Any other code/configuration required can be provided
Update 1
Using the "me" based endpoint (to default to self's folder) results in a 403 Forbidden, even though I am sending a valid access token. Have granted all permissions for file access (see attached image). The Grant Permission is successful for all demanded permissions.
Related
I have an ASP.NET 6.0 Web API project. I would like to add authentication and authorization to it, but it must use SSO via Azure.
We already have a SPA application that does this, it uses the Angular MSAL library to redirect the user to an SSO Login page, then returns to the SPA with an access token. The access token is then added to the header of each request to the Web API, which uses it to enforce authentication.
Now we want to share our web API with other teams within our organization, and we would like to have that login process just be another API call, rather than a web page.
Conceptually, a client would hit the /login endpoint of our API, passing in a userID and password. The web API would then get an access token from Azure, then return it as the payload of the login request. It's then up to the client to add that token to subsequent request headers.
I have done this with regular ASP.NET Identity, where all of the user and role data is stored in a SQL database, but since our organization uses SSO via Azure Active Directory, we would rather use that.
I have researched this topic online, and so far all of the examples I have seen use a separate SPA, just like we already have. But as this is a web api, not a front-end, we need to have an API method that does this instead.
Is this even possible? I know Microsoft would rather not have user credentials flow through our own web server, where a dishonest programmer might store them for later misuse. I understand that. But I'm not sure there's a way around this.
Thanks.
I believe you are looking for the Resource Owner Password (ROP) flow. You can use IdentityModel.OidcClient to implement it.
Sample code:
public class Program
{
static async Task Main()
{
// call this in your /login endpoint and return the access token to the client
var response = await RequestTokenAsync("bob", "bob");
if (!response.IsError)
{
var accessToken = response.AccessToken;
Console.WriteLine(accessToken);
}
}
static async Task<TokenResponse> RequestTokenAsync(string userName, string password)
{
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync(Constants.Authority);
if (disco.IsError) throw new Exception(disco.Error);
var response = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "roclient",
ClientSecret = "secret",
UserName = userName,
Password = password,
Scope = "resource1.scope1 resource2.scope1",
Parameters =
{
{ "acr_values", "tenant:custom_account_store1 foo bar quux" }
}
});
if (response.IsError) throw new Exception(response.Error);
return response;
}
}
Sample taken from IdentityServer4 repository where you can find more ROP flow client examples.
I would recommend that you don't go with this implementation and instead have all clients obtain their access tokens directly from Azure AD like you did with your Angular SPA.
In scope of a compliance monitoring app for our powerapps usage, we created a C# console app which crawls powerapps.
environments
applications
permissions
(similar REST call than powershell commands Get-AdminPowerAppEnvironment Get-AdminPowerApp provided by Microsoft.PowerApps.Administration cmdlets)
proof of concept was done by stealing the Bearer header from fiddler when connected with my admin AAD account. POC is now validated, time to make it clean. And as often with AAD auth flow (for me), it's more complex than expected (sum up of hours of try & fails).
I find really little internet reference on how to authenticated & crawl (this part is ok) this API.
I tried different auth workflow and lib
MSAL
ADAL
fiddler on top of powershell command (but in powershell I'm not using a service principal)
and either I can't spot the correct scope or my service principal has no permission on the resource.
I have an App registration called AAA powerapps with ... quite a lot of permission (try & fails)
Created a client secret
just in case, put into Power Apps administrator
string authority = $"https://login.microsoftonline.com/[tenant-guid]/";
var app = ConfidentialClientApplicationBuilder
.CreateWithApplicationOptions(new ConfidentialClientApplicationOptions { ClientId = "[client-id]", ClientSecret = "[shhuuuu]" })
.WithAuthority(authority).Build();
// tried with https://management.azure.com/.default / https://api.bap.microsoft.com/.default / https://service.powerapps.com./default
var token = app.AcquireTokenForClient(new[] { "https://management.azure.com/.default" }).ExecuteAsync().Result;
//var client = new RestClient("https://api.bap.microsoft.com/providers/Microsoft.BusinessAppPlatform/scopes/admin/environments?api-version=2016-11-01");
var client = new RestClient("https://management.azure.com/providers/Microsoft.BusinessAppPlatform/scopes/admin/environments?api-version=2016-11-01");
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Bearer " + token.AccessToken);
IRestResponse response = client2.Execute(request);
I get a token, but I don't think it's on the correct scope/resource unfortunately.
Depending on the scope, I either get
Forbidden
{"error":
{"code":"Forbidden","message":"The service principal with id '[service principal guid (not client id)'
for application <null> does not have permission to access the path 'https://api.bap.microsoft.com:11779/providers/Microsoft.BusinessAppPlatform/scopes/admin/environments?api-version=2016-11-01' in tenant [tenant-guid]."}}
or
Unauthorized
{"error":{"code":"AuthenticationFailed","message":"Authentication failed."}}
Didn't succeed with client id & client secret but managed to call api.bap.microsoft.com/../Microsoft.BusinessAppPlatform with AAD user.
eg https://api.bap.microsoft.com/providers/Microsoft.BusinessAppPlatform/scopes/admin/environments?api-version=2016-11-01
re-used same method as Microsoft.PowerApps.Administration cmdlets
AAD account with AAD Power platform administrator role
If Multi Factor Access enabled for admin, create exception rule
use ADAL nuget Microsoft.IdentityModel.Clients.ActiveDirectory
dotnet
AuthenticationContext authContext = new AuthenticationContext("https://login.windows.net/common");
var credentials = new UserPasswordCredential("admin_powerapps#domain.net", "password");
// "1950a258-227b-4e31-a9cf-717495945fc2" = client ID for Azure PowerShell.
// available for any online version
var token = authContext.AcquireTokenAsync("https://management.azure.com/", "1950a258-227b-4e31-a9cf-717495945fc2", credentials).Result;
// any REST call
Header "Authorization" : "Bearer " + token.AccessToken
Endpoint : https://api.bap.microsoft.com/providers/Microsoft.BusinessAppPlatform/scopes/admin/environments?api-version=2016-11-01
results
The benefit from this method is that it can crawl "all" environments seamlessly (not sure the approach with creating a user on each was working).
Another approach I spotted was using Powerapps connector for admin but it involved extra configuration on each environments and required a license.
Not fully sure this method would be supported long term (eg xx.windows.net). Open minded for any other suggestion.
I was facing the same issue. What solved it with me is to register the app with tenant admin rights on the power platform admin using this command New-PowerAppManagementApp. Please, find MSFT article here.
After your client application is registered in Azure AD, it also needs to be registered with Microsoft Power Platform. Today, there's no way to do this via the Power Platform admin center; it must be done programmatically via Power Platform API or PowerShell for Power Platform administrators. A service principal cannot register itself—by design, the application must be registered by an administrator username and password context. This ensures that the application is created knowingly by someone who is an administrator for the tenant.
$appId = "CLIENT_ID_FROM_AZURE_APP"
# Login interactively with a tenant administrator for Power Platform
Add-PowerAppsAccount -Endpoint prod -TenantID $tenantId
# Register a new application, this gives the SPN / client application same permissions as a tenant admin
New-PowerAppManagementApp -ApplicationId $appId
I am trying to call a web (api) service using a OAuth2 token based on a AAD managed user account logged in to an AAD joined machine using ADAL.Net - specifically using this example:
https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/wiki/AcquireTokenSilentAsync-using-Integrated-authentication-on-Windows-(Kerberos)
However I keep getting the exception: Unknown User Type
In my setup I have logged onto a machine inside an AAD private network with a synced AAD user account. I then run the example code using WindowsAuthentication.
After some debugging I can narrow the exception to be thrown from this method in ADAL.Net
protected internal /* internal for test only */ override async Task PreTokenRequestAsync()
{
await base.PreTokenRequestAsync().ConfigureAwait(false);
if (!SupportADFS)
{
var userRealmResponse = await _commonNonInteractiveHandler.QueryUserRealmDataAsync(Authenticator.UserRealmUriPrefix)
.ConfigureAwait(false);
if (string.Equals(userRealmResponse.AccountType, "federated", StringComparison.OrdinalIgnoreCase))
{
WsTrustResponse wsTrustResponse = await _commonNonInteractiveHandler.PerformWsTrustMexExchangeAsync(
userRealmResponse.FederationMetadataUrl,
userRealmResponse.CloudAudienceUrn,
UserAuthType.IntegratedAuth).ConfigureAwait(false);
// We assume that if the response token type is not SAML 1.1, it is SAML 2
_userAssertion = new UserAssertion(wsTrustResponse.Token, (wsTrustResponse.TokenType == WsTrustResponse.Saml1Assertion) ? OAuthGrantType.Saml11Bearer : OAuthGrantType.Saml20Bearer);
}
else
{
throw new AdalException(AdalError.UnknownUserType);
}
}
}
Since everything in my setup is managed with AAD I do not see why the user account type needs to be "federated" in order for a token to be retrieved.
So I suspect that I need to get my token in another way!?
Any help will be appreciated ;)
After investigating we found that the above code (ADAL.Net) can only be used with a federated setup.
Federation means that you have an on premise network - which holds your windows user accounts - connected to an Azure AD network - which then "federates" these accounts to Azure AD. However it would be good to have a member of the ADAL team to comment on this.
Obtaining a token for a windows user account in a pure Azure AD (Managed setup) can supposedly be done using this code:
var connString = $"RunAs=App;AppId={appId};TenantId={tenantId};AppKey={appKey};";
var azureServiceTokenProvider = new AzureServiceTokenProvider(connString2);
var accessToken = azureServiceTokenProvider.GetAccessTokenAsync(service, tenantId).Result;
which is descripbed here: https://learn.microsoft.com/en-us/azure/key-vault/service-to-service-authentication#running-the-application-using-managed-identity
Again its not that well documented so any clarity from microsoft would be good.
I have enabled AAD Authentication for an Azure Function and then tried to consume the Function App (HTTP Trigger) in a web application but getting Unauthorized issue.
I also tried consuming it by creating a function proxy but the issue still persists.
Process Followed:
Created two AD Application (Web App, Azure Functions) and gave the
permission of Azure Functions AD to the Web App AD Created a basic
http trigger function
Enabled Authentication for Azure Functions by providing the details of Azure
Functions
Created a web application and during the access token generation, provided
the Client ID,Secret of web application and Audience URI( App ID) of Azure F
Unctions AD.
ClientCredential clientCredential = new ClientCredential(ConfigurationManager.AppSettings["ida:ClientId"], ConfigurationManager.AppSettings["ida:SecretKey"]);
AuthenticationContext authContext = new AuthenticationContext(Startup.Authority);
AuthenticationResult result = await authContext.AcquireTokenAsync(ConfigurationManager.AppSettings["azrfunc:ResourceID"], clientCredential);
string requestUrl = "https://xxxx.azurewebsites.net/api/HttpTriggerCSharp1?code=Gxxxxx==&name=xxxx";
// Make the GET request
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = client.SendAsync(request).Result;
According to your description, I assumed that you are using Authentication and authorization in Azure App Service for your azure function app.
And as How authentication works in App Service states as follows:
Users who interact with your application through a web browser will have a cookie set so that they can remain authenticated as they browse your application. For other client types, such as mobile, a JSON web token (JWT), which should be presented in the X-ZUMO-AUTH header, will be issued to the client. The Mobile Apps client SDKs will handle this for you. Alternatively, an Azure Active Directory identity token or access token may be directly included in the Authorization header as a bearer token.
Based on your scenario, I created my two aad apps and set the required permission for my web app to access the aad app of my function app as follows:
And enable AAD authentication for my azure function app as follows:
Then getting the access token by using the following code:
var clientCredential = new ClientCredential("{clientId-for-my-web-app}", "{clientSecret-for-my-web-app}");
var authContext = new AuthenticationContext("https://login.windows.net/{tenantId}");
var result = await authContext.AcquireTokenAsync("{clientId-for-my-function-app}", clientCredential);
TEST:
In summary, you could decode your access token by using https://jwt.io/ and check the aud as follows:
Moreover, I noticed that your requestUrl contains the query string code. If you both enable the function level authorization and the user-based authentication, you also need to make sure your function key or master key is correct. Also, you could just set the anonymous authorization level for your azure function and just leverage the user-based authentication.
I have a problem (or two) with regards to accessing my office 365 account via the Microsoft Graph API.
The first issue is that I have a java program that is attempting to list all users in the office 365 subscription. I am calling https://graph.microsoft.com/v1.0/users/ but getting a 403 forbidden back.
On the App registration, I have added permissions including User.Read, User.ReadBasic.All, User.ReadWrite on both delegated and app permissions.
I have also tried to use the Graph Explorer, but when I enter to use my account it still uses the built in graph user and doesn't show my application login info. Not sure if these are related.
Here is code snippet that results in a 403
AuthenticationResult result = getAccessTokenFromUserCredentials(RESOURCE_GRAPH, ID, PASSWORD);
URL url = new URL("https://graph.microsoft.com/v1.0/users/") ;
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("Authorization", "Bearer "+result.getAccessToken());
if (conn.getResponseCode() != 200) {
throw new RuntimeException("Failed : HTTP error code : "
+ conn.getResponseCode());
}
And here is the method that gets the token
private static AuthenticationResult getAccessTokenFromUserCredentials(String resource,
String username, String password) throws Exception {
AuthenticationContext context;
AuthenticationResult result = null;
ExecutorService service = null;
try {
service = Executors.newFixedThreadPool(1);
context = new AuthenticationContext(AUTHORITY, false, service);
Future<AuthenticationResult> future = context.acquireToken(
resource, CLIENT_ID, username, password,
null);
result = future.get();
} finally {
service.shutdown();
}
if (result == null) {
throw new ServiceUnavailableException(
"authentication result was null");
}
return result;
}
The app register in apps.dev.microsoft.com works with the v2.0 endpoint .Please click here for more details about the v2.0 endpoint .
You can acquiring token using v2.0 authentication protocols and Azure Active Directory v2.0 authentication libraries . During authentication , you need to do user consent or admin consent for User.ReadBasic.All permission . After consenting , access token includes that delegate permission and will work when calling list users operation .
OK, thought I should post up the answer. Firstly, and most confusingly, the apps.dev.microsoft.com registration didn't seem to work (even though I was using the V2.0 endpoint and the version 2 libraries).
However, when I registered the app using the azure portal directly, this fixed the issue. I have subsequently been able to access the service correctly.
It seems strange that, although the authentication / authorisation service was standard for my app and worked perfectly for accessing Sharepoint / One Drive etc, but, when wanting to hit the users endpoint, it would only work if it was registered in the portal.azure.com.
Many thanks everyone for your help.