When I configure an Authentication prompt to connect to Azure AD:
AuthenticationContext authContext = new AuthenticationContext(authority);
var result = authContext.AcquireTokenAsync(resource (using the clientID),
clientId,
redirectUri,
new PlatformParameters(PromptBehavior.SelectAccount))
.Result;
It fails with the error:
"AADSTS7000218: The request body must contain the following parameter:
'client_assertion' or 'client_secret'"
Should a client_secret need to be configured when we are connecting by user, not client, credentials and if so how does it need to be configured?
No, client_secret needn't to be configured when we are connecting by user.
Just add a desktop+devices platform with a redirect url provided or use the default one. I am using the default one.
Related
I have created a service principal in AAD and am able to assign to the workspace manually in https://app.powerbi.com/home
I want to assign the service principal to all the workspaces programmatically.
Is there any way to do it?
Please help
Thanks
Yes, you can use the Power BI REST API and call Update Group User to add the service principal to the workspace:
Request:
PUT https://api.powerbi.com/v1.0/myorg/groups/f089354e-8366-4e18-aea3-4cb4a3a50b48/users
Request body:
{
"identifier": "1f69e798-5852-4fdd-ab01-33bb14b6e934",
"groupUserAccessRight": "Admin",
"principalType": "App"
}
To use the API, you must authenticate yourself, for example with ADAL or MSAL. Here is an example how to get an access token with MSAL:
private static async Task<string> GetToken()
{
// TODO: Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory -Version 2.21.301221612
// and add using Microsoft.IdentityModel.Clients.ActiveDirectory
//The client id that Azure AD created when you registered your client app.
string clientID = "{Client_ID}";
//RedirectUri you used when you register your app.
//For a client app, a redirect uri gives Azure AD more details on the application that it will authenticate.
// You can use this redirect uri for your client app
string redirectUri = "https://login.live.com/oauth20_desktop.srf";
//Resource Uri for Power BI API
string resourceUri = "https://analysis.windows.net/powerbi/api";
//OAuth2 authority Uri
string authorityUri = "https://login.microsoftonline.com/common/";
//Get access token:
// To call a Power BI REST operation, create an instance of AuthenticationContext and call AcquireToken
// AuthenticationContext is part of the Active Directory Authentication Library NuGet package
// To install the Active Directory Authentication Library NuGet package in Visual Studio,
// run "Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory" from the nuget Package Manager Console.
// AcquireToken will acquire an Azure access token
// Call AcquireToken to get an Azure token from Azure Active Directory token issuance endpoint
AuthenticationContext authContext = new AuthenticationContext(authorityUri);
var token = authContext.AcquireTokenAsync(resourceUri, clientID, new Uri(redirectUri)).Result.AccessToken;
Console.WriteLine(token);
Console.ReadLine();
return token;
}
This token must be added to the request headers, when you call the API:
//Add token to the request header
request.Headers.Add("Authorization", String.Format("Bearer {0}", token));
I'm composing a demo on how to obtain a token from IDS4 using Postman.
The request for password token is taken from IDS4's page.
[HttpGet("token")]
public IActionResult GetToken([FromHeader] string user, [FromHeader] string pass)
{
string tokenEndpoint = "https://localhost:44300/connect/token";
HttpClient client = new HttpClient();
Task<TokenResponse> tokenResponse =
client.RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = tokenEndpoint,
ClientId = "client",
ClientSecret = "client_secret",
Scope = "MemberApi.full",
UserName = user,
Password = pass
});
TokenResponse toko = tokenResponse.Result;
if (toko.IsError)
return Ok(toko.Error);
return Ok(toko.AccessToken;
}
The clients are set up as follows.
private static IEnumerable<Client> GetClients => new[]
{
...
new Client
{
ClientId = "client",
ClientSecrets = { new Secret("client_secret".Sha256()) },
ClientName = "Client",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { "http://localhost:5000/security/credentials" },
PostLogoutRedirectUris = { "http://localhost:5000/index.html" },
AllowedCorsOrigins = { "http://localhost:5000", "https://localhost:44300" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"MemberApi",
"MemberApi.full",
"MemberApi.limited"
}
}
};
The API resources are set up as the following shows.
private static IEnumerable<ApiResource> GetApis => new[]
{
new ApiResource
{
Name = "MemberApi",
DisplayName = "Members' API",
ApiSecrets = {new Secret("MemberSecret".Sha256())},
UserClaims = {JwtClaimTypes.Name, JwtClaimTypes.Email, JwtClaimTypes.Role},
Scopes = {new Scope("MemberApi.full"), new Scope("MemberApi.limited")}
}
};
As far I can tell I followed the suggestions in the docs. I've tried to compare with the examples, too. Despite that, I get stuck on the error saying unauthorized_client. What can I be missing?
The client request is not allowed in this flow:
AllowedGrantTypes = GrantTypes.Implicit
Forget client.RequestPasswordTokenAsync. You don't need it and you can't use it. In the implicit flow only the user knows the password. It is out of reach for the client.
Assume IdentityServer runs on one domain: https://idp.mydomain.com and the client runs somewhere else: https://mvc.mydomain.com
When the user hits a secured page on the mvc client, the user is routed to IdentityServer where the user logs in. There the user enters the credentials and if succesful the user is returned to the client as a known identity.
Depending on the flow the client eventually ends up with at least an access token. This token is important because that allows the client to access the resource on behalf of the user. It is like an entry ticket.
Based on the access token the resource now knows WHO wants to access the resource. The access token has one claim that makes this distinction, the 'sub' claim. Without this claim the client has no access to the resource in this flow.
In your configuration the client is allowed to access the 'MemberApi' scopes, but it needs the user's consent before it actually can access the resource.
If you want to retrieve a token start with the easiest flow there is, the client credentials flow.
That's the flow where there is no user at all. The client (as in piece of software) can login using the clientid + secret. When configured properly this will result in an access token.
Now the client can access the resource without any user interaction. The identity token is not available as there is no user. The 'sub' claim is missing. The refresh token is not supported in this flow, it doesn't need it. The client can request a new token using the credentials.
If you want to know how the refresh token works, in a hybrid flow the user logs in and in addition (if scope=offline is configured) a refresh token is returned.
As an access token is only valid for a short time (depends on the expiration time) a new token must be acquired. For this the refresh token should be used. The refresh token allows the client to request a new access token without requiring user interaction (offline access).
The new access token is used until it expires and a new token must be requested. Until the refesh token itself expires, but that can be configured.
In the implicit flow there is no refresh token, but the access token does expire all the same. So you'll need another way to refresh the token. For that you can use something like a silent token renew implementation.
For terminology please read the documentation.
Please note the various flows. It all depends on the circumstances. Is there a user or not, is it a browser application, is there a front-channel, back-channel, is offline access required, can the client keep a secret? Things that need to be considered before choosing a flow.
When a flow is chosen, you need to configure the allowed grants for clients. A client using client credentials cannot access the resource if only an implicit grant type is allowed.
IdentityServer is mostly about configuring clients and resources. Take a look at the samples to see the different flows and how they are configured.
The client is only allowed to use Implicit Flow to acquire token for accessing the resource which protected by Identity Server :
AllowedGrantTypes = GrantTypes.Implicit,
But your client is using the Resource Owner Flow :
This grant type is suitable for clients capable of obtaining the
resource owner's credentials (username and password, typically using
an interactive form). It is also used to migrate existing clients
using direct authentication schemes such as HTTP Basic or Digest
authentication to OAuth by converting the stored credentials to an
access token.
If you are using SPA application , you should use Implicit Flow to obtain tokens without exposing end user credentials to a third party.
Generally , you have three apps : client app , identity server(with user db) and api . When using Implicit Flow :
Client will redirect user to identity server app, identity server provides UI to let user enter their credentials .
After user enter their credential , identity server will validate the credential in DB/configuration file . Of course you can also config the external login in identity server .
After validate the credentials , identity server will issue ID Token and access token(if scope includes API resource) back to your client app ,according to the callback url in client's OpenID Connect configuration .
Client app will validate and decode the ID token and sign-in user . You can use SDK or directly handle the process manually .
If you get the acess token , you can keep the access token in session cache , it can be used to access the protected resource until it is expires .
I'm creating a subscription request to Outlook Push Notification using this URL :
https://outlook.office365.com/api/v2.0/me/subscriptions
I have used certificate based authentication with Azure AD to acquire token using the following code:
AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com/mytenant/oauth2/v2.0/token");
AuthenticationResult authResult = await authContext.AcquireTokenAsync("https://outlook.office365.com/", certificate);
When calling the subscription API I'm getting a Forbidden error with the following response:
x-ms-diagnostics: 2000008;reason="OAuth2 AccessTokens for app-only require that the target mailbox is explicitly specified with the api call";error_category="invalid_grant"
Could you please help me with where I'm going wrong or how to debug / solve this error.
Thanks in advance
Since you are using App-only tokens you are required by the API to explicitly specify the target mailbox in the URI.
Eg: https://outlook.office365.com/api/v2.0/Users(foo#bar.com)/subscriptions
me is only useful in case the target mailbox is same as authenticated user. However in case of app-only there is no authenticated user, do you have to be explicit.
I have a web application. In the home page, user will enter the credentials, and system should validate against Azure AD and proceed further.
When I use a native app, and use UserCredentials, it validates the user, but if I use same approach for WebAPI, it throw the exception
The request body must contain the following parameter: 'client_secret
or client_assertion'
When I use the WebAPI using clientCredentials, it generates the accessToken, which do not validate the user credentials. I also tried passing the credentials as part of httpclient headers in the consequent calls, it is working despite the wrong credentials.
string AzureADSTSURL = "https://login.windows.net/{0}/oauth2/token?api-version=1.0";
string GraphPrincipalId = "https://graph.windows.net";
string userid = "userid";
string password = "pass";
string tenantId = "axxx"; // webapi
string clientId = "bxxx";
string clientSecret = "cxxx";
string authString = String.Format(AzureADSTSURL, tenantId);
var context = new AuthenticationContext(authString);
UserCredential userCredentials = new UserCredential(userid, password);
AuthenticationResult authenticationResult = context.AcquireToken(GraphPrincipalId.ToString(), clientId, userCredentials); // this works only if the clientId corresponds to a native app
ClientCredential clientCredential = new ClientCredential(clientId, clientSecret);
AuthenticationResult result = context.AcquireToken(GraphPrincipalId, clientCredential);
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(result.AccessToken, Convert.ToBase64String(UTF8Encoding.UTF8.GetBytes(userid + ':' + password)));
httpClient.GetAsync("http://localhost:11455/Login.aspx");
Is there a way to validate the credentials without using native app? Graph API is not a right choice for this I believe.
I was trying to do the same thing, and ran into the same error:
The request body must contain the following parameter: 'client_secret or client_assertion'
I banged my head on it for a while, and then hit up AzureSupport on twitter.
Turns out this type of auth is only supported if you set up the Azure AD App as Native Client Application. If you set it up as a Web Application then you get that error because the only way to access a web application in Azure AD is via client ID + secret.
You can have multiple apps on top of a single AD, so you can just set up a second app as native client to authenticate the same users in that directory.
You can certainly use WebAPI. Here's how to set it up:
If you use Azure Web Apps, which supports ASP.NET MVC then you can use the Azure Active Directory authentication mechanism. Here is a blog post describing how to set it up: https://azure.microsoft.com/en-us/documentation/articles/app-service-mobile-how-to-configure-active-directory-authentication/
Once you have that, auth will be enabled for your app and you can configure the AAD app in the portal. See this blog post for more details: http://blogs.technet.com/b/ad/archive/2014/12/18/azure-active-directory-now-with-group-claims-and-application-roles.aspx
Here is an example which shows how to read AAD group claims from a web app: https://github.com/Azure-Samples/active-directory-dotnet-webapp-groupclaims
Once you have the tokens, you can then call a Web API, which is shown by this example: https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect
There's a good list of AAD examples here: https://azure.microsoft.com/en-us/documentation/articles/active-directory-authentication-scenarios/
Short answer: No
I would consider this article to be the authoritive answer as to why.
No web sites/confidential clients
This is not an ADAL limitation, but an AAD setting. You can only use those flows from a native client. A confidential client, such as a web site, cannot use direct user credentials.
Direct use of username an password is [...] a bit of a Faustian pact – the price you pay for its directness is in the many limitations it entails and the reduced flexibility that it imposes on any solution relying on it.
I have an Azure API App marked as "Public (authenticated)" and set up an Azure Active Directory identity in the associated gateway as detailed in Protect an API App.
I then created a native application in the same Azure Active Directory Tenant and added permission to access the Gateway in the delegated permissions.
Using ADAL and the following code, I'm able to successfully authenticate and get an access token, but I can't figure out how to use it to access my API app.
string Tenant = "[xxx].onmicrosoft.com";
string Authority = "https://login.microsoftonline.com/" + Tenant;
string GatewayLoginUrl = "https://[gateway].azurewebsites.net/login/aad";
string ClientId = "[native client id]";
Uri RedirectUri = new Uri("[native client redirect url]");
async Task<string> GetTokenAsync()
{
AuthenticationContext context = new AuthenticationContext(Authority);
PlatformParameters platformParams = new PlatformParameters(PromptBehavior.Auto, null);
AuthenticationResult result = await context.AcquireTokenAsync(GatewayLoginUrl, ClientId, RedirectUri, platformParams);
return result.AccessToken;
}
I've tested the API app manually entering an x-zumo-auth header I get in Chrome and it works then, but not with a token I get using ADAL. I've also tried the browser forms described in their sample code which works but doesn't give me a refresh token.
How do I need to set up my authentication code so I can use a TokenCache and ADAL with my API app?
Generally you pass the access token in the Authorization header when when calling a web api:
Authorization: Bearer ThisIsTheAccessTokenYouRecievedFromADAL
You may want to use AppServiceClient to authenticate the user and invoke a protected API App endpoint. Install Microsoft.Azure.AppService SDK (-pre) Nuget package to your client project.
You can find more details in the AzureCards samples on GitHub - https://github.com/Azure-Samples/API-Apps-DotNet-AzureCards-Sample