Azure AD B2C get token programatically for unit testing - c#

My scenario is simple I have a simple Azure Function with B2C authentication on it and I'm writing unit tests but I found an issue, I'm not able to authenticate to the azure functions programmatically.
I'm able to access through the browser and even I can grab the token and put it into the unit test and it works fine, but when I try to generate a token using the ClientID, TenantID, etc. I get a token, but 401 Unauthorized response on the Azure functions.
Is there a way to generate a valid B2C token programmatically (without login in the browser?
The approach I'm using so far:
public static async Task<AuthenticationResult> GetAccessToken(string resourceUri, string clientId, string clientSecret)
{
ClientCredential clientCredential = new ClientCredential(clientId, clientSecret);
string aadInstance = "https://login.microsoftonline.com/";
string tenant = "<mytenant>.onmicrosoft.com";
string authority = string.Concat(aadInstance, tenant);
AuthenticationContext authContext = new AuthenticationContext(authority);
return await authContext.AcquireTokenAsync(resourceUri, clientCredential);
}
I'm getting a token (EY.......) but is not valid, when I passed to the Azure Function request, it returns 401 Unauthorized.
Thanks in advance!
Ivan

A couple of months ago, Microsoft released a policy for resource owner password credentials flow, with that policy you can simulate a login passing the login details in a query as follows:
Create a ROPC policy in B2C
Register an application
Test the policy as follows:
https://te.cpim.windows.net/{B2C TENANT}/{ROPC B2C POLICY}/oauth2/v2.0/token?username={USERNAME}&password={password}&grant_type=password&scope=openid+{CLIENT ID}+offline_access&client_id=[CLIENT ID]&response_type=token+id_token
You can find more detailed info here

Your unit test is acquiring a token from the Azure AD v1.0 endpoint rather than the Azure AD B2C v2.0 endpoint.
Your Azure function is expecting the token to be issued by the Azure AD B2C v2.0 endpoint.
In the short term, you can consider acquiring the token from the Azure AD B2C v2.0 endpoint by replaying the browser requests using the HttpClient class.
In the near term, support for the resource owner password credential grant by Azure AD B2C will enable your unit test to acquire a token from the Azure AD B2C v2.0 endpoint by POSTing a user credential to the endpoint.

Related

Getting error AADST50013 for OBO flow using Azure AD B2C on .Net Core 3.1

I am following this official MS doc to implement the OBO flow for two secure Web APIs (let's say Web API 1 and 2) using Azure AD B2C. That previous link points out to the following example on Git.
Basically, I am using the same code:
MyController.cs
string[] scopes = { "profile.read.basic", "user.read" };
UserProfile profile = null;
try
{
string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenantId);
ClaimsPrincipal principal = HttpContext.User as ClaimsPrincipal;
//Grab the Bearer token from the HTTP Header using the identity bootstrap context. This requires SaveSigninToken to be true at Startup.Auth.cs
var bootstrapContext = principal.Identities.First().BootstrapContext?.ToString();
// Creating a UserAssertion based on the Bearer token sent by TodoListClient request.
//urn:ietf:params:oauth:grant-type:jwt-bearer is the grant_type required when using On Behalf Of flow: https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow
UserAssertion userAssertion = new UserAssertion(bootstrapContext, "urn:ietf:params:oauth:grant-type:jwt-bearer");
// Creating a ConfidentialClientApplication using the Build pattern (https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/Client-Applications)
var app = ConfidentialClientApplicationBuilder.Create(clientId)
.WithAuthority(authority)
.WithClientSecret(appKey)
.WithRedirectUri(redirectUri)
.Build();
// Acquiring an AuthenticationResult for the scope user.read, impersonating the user represented by userAssertion, using the OBO flow
AuthenticationResult result = await app.AcquireTokenOnBehalfOf(scopes, userAssertion).ExecuteAsync();
On StartUp.cs I had to set up the SaveSigninToken to true
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(options =>
{
Configuration.Bind("AzureAdB2C", options);
options.TokenValidationParameters = new TokenValidationParameters { SaveSigninToken = true };
}, options => { Configuration.Bind("AzureAdB2C", options); });
When I run the Web API using Swagger and hit the tested endpoint the following line of code:
AuthenticationResult result = await app.AcquireTokenOnBehalfOf(scopes, userAssertion).ExecuteAsync();
thrown the following error:
AADSTS50013: Assertion failed signature validation. [Reason - The key was not found.]
Trace ID: c0d53284-12f3-4ab0-a42c-d7c35e2ad300
Correlation ID: e37849e8-938b-441e-bd80-d1612733dc17
Timestamp: 2021-08-20 22:13:53Z
From Azure AD B2C I have granted permission to Web API 1 from the App Registration to communicate to the Web API 2.
For the given error, I have been doing some research/ investigation as well as trying a few different approaches but no luck.
Does any one know how to troubleshoot this?
Thanks in advance
If these really are tokens issued via Azure AD B2C endpoint, then it simply is not compatible.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/custom-policy-developer-notes#oauth-20-application-authorization-flows
Your error code, however, shows you are trying to use the Azure AD endpoint, probably of your Azure AD B2C tenant.
In which case, you seem to be using an Azure AD B2C signed token, and trying to do OBO against the Azure AD endpoint, and both use different signing keys. This therefore will not work.
Only the Azure AD endpoint will do OBO flow. I.e. Users tokens issued from Azure AD token endpoint, and performing OBO against the same endpoint with the users AAD token.
AAD endpoint: https://login.microsoftonline.com/<tenant>/oauth2/v2.0/token
AAD B2C endpoint: https://contoso.b2clogin.com/<tenant>/<B2C-policy-id>/oauth2/v2.0/token
You can't use these two endpoints together for different things.

Why doesn't AcquireTokenByAuthorizationCode return RefreshToken

In this documentation it gives a complete flow for a web application that calls a web API:
The web application executes a policy and the user completes the user experience.
Azure AD B2C returns an (OpenID Connect) id_token and an authorization code to the browser.
The browser posts the id_token and authorization code to the redirect URI.
The web server validates the id_token and sets a session cookie.
The web server asks Azure AD B2C for an access_token by providing it with the authorization code, application client ID, and
client credentials.
The access_token and refresh_token are returned to the web server.
The web API is called with the access_token in an authorization header.
The web API validates the token.
Secure data is returned to the web application.
Looking at 6. and using the code in the Azure-Samples repository active-directory-b2c-dotnet-webapp-and-webapi
, I cannot get the line
AuthenticationResult result = await confidentialClient.AcquireTokenByAuthorizationCode(Globals.Scopes, notification.Code).ExecuteAsync();
to return a refresh_token. It returns an IdToken and AccessToken but no RefreshToken.
By using my browser and Postman and following the steps in this document with the same B2C tenant and application I do get the refresh token as expected.
This question is similar to mine and the blog post mentioned in one of the answers provides a work around to the symptom of not having a refresh token but my question remains:
How can I get AcquireTokenByAuthorizationCode to return a refresh_token?
To get refresh token, your application should append offline_access as scope.
You mentioned like this msdn able to return you refresh token. It is because request already contain offline_access scope
&scope=openid%20offline_access
To get refresh token from active-directory-b2c-dotnet-webapp-and-webapi. You need to update Global.cs Scopes filed to include offline_access
public static string[] Scopes = new string[] { ReadTasksScope, WriteTasksScope, "offline_access" };
The offline_access scope is optional for web apps. It indicates that your app needs a refresh token for long-lived access to resources.
Go to web.config add below:
<add key ="api:OfflineAccessScope" value="offline_access "/>
And in Global.cs :
public static string OfflineAccessScope = ApiIdentifier + ConfigurationManager.AppSettings["api:OfflineAccessScope"];
public static string[] Scopes = new string[] { ReadTasksScope, WriteTasksScope, OfflineAccessScope};
Then the Globals.Scopes in AcquireTokenByAuthorizationCode will return refresh token.

Azure AD authentication token fails web api authorization

I have a web-api on Azure that requires authorization and I am using Azure AD to authenticate accounts and generate access tokens.
I can successfully acquire access tokens from Azure AD with ADAL for the same account in two different ways, but only one of them is authorized by the web-api, the other one fails.
The following is authenticating an account interactively and the token is authorized by the web-api
result = AuthenticationContext.AcquireTokenAsync(resource, clientId, redirectUri, new PlatformParameters(PromptBehavior)).Result;
where resource is web-api application id (guid).
The following is authenticating an account non-interactively with a given user name and password, but the token is not authorized by the web api
UserPasswordCredential cred = new UserPasswordCredential(userName, password);
result = AuthenticationContext.AcquireTokenAsync(resource, clientId, cred).Result;
where resource = https://{tenant}/{api name}.
The web-api call is as follows:
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = await httpClient.GetAsync(ApplicationCallUri);
Both ways return identical AuthenticationResult objects (apart from tokens and time stamps) and I cannot see why authorization fails for the second one.
The web-api response is "Authorization has been denied for this request."
Since authentication succeeds for both ways, I assume it must be something with at the web-api's side. Help is much appreciated. Thanks.
Thanks to juunas who pointed out the audience parameter I realized that the web api was set to expect tokens for only one the two audience values. I added a second option for bearer authentication and it works for both scenarios.
Thank you juunas!

Is it possible to purge azure cdn endpoint using storage key?

I want to create a function which will purge a file on Azure CDN.
Here in the documentation It says How can I purge the content specifying the path.
POST https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Cdn/profiles/{profileName}/endpoints/{endpointName}/purge?api-version=2017-10-12
But the security is provided by Azure Active Directory OAuth2 Flow.
Hence I need to use clientId, secretId (from here https://blogs.msdn.microsoft.com/maheshk/2017/04/01/azure-cdn-how-to-purge-cdn-content-from-c-code/)
var authenticationContext = new AuthenticationContext("https://login.microsoftonline.com/microsoft.onmicrosoft.com");
ClientCredential clientCredential = new ClientCredential(clientId, clientSecret);
Task<AuthenticationResult> resultstr = authenticationContext.AcquireTokenAsync("https://management.core.windows.net/", clientCredential);
WebClient client = new WebClient();
//authentication using the Azure AD application
var token = resultstr.Result.AccessToken;
I wander Is there a way to make purge request using storage key and not clientId, secretId?
No, it is not possible. The Azure Rest API Endpoints - Purge Content is integrated with Azure AD authentication, it needs your valid credentials to get the access token.
See this link : Getting Started with REST - Register your client application with Azure AD.
Most Azure services (such as Azure Resource Manager providers and the classic deployment model) require your client code to authenticate with valid credentials before you can call the service's API. Authentication is coordinated between the various actors by Azure AD, and provides your client with an access token as proof of the authentication. The token is then sent to the Azure service in the HTTP Authorization header of subsequent REST API requests. The token's claims also provide information to the service, allowing it to validate the client and perform any required authorization.

OpenIDConnect to Connect to Azure AD

I am using OWIN OpenID Connect Middleware to connect to Azure AD. I am able to authenticate the user successfully and redirect back to callback endpoint. I am a bit confused here as i am receiving only id_token & code in the response.
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "Azure AD - TEST",
Caption = "azure AD",
SignInAsAuthenticationType = signInAsType,
ClientId = "some guid",
Authority = "https://sts.windows.net/idp",
ResponseType = OpenIdConnectResponseTypes.CodeIdToken,
RedirectUri = "https://localhost:44392/ExternalLogins/Callback/",
AuthenticationMode = AuthenticationMode.Active,
});
Callback Method :
[HttpPost]
[Route("ExternalLogins/Callback")]
[AllowAnonymous]
public async Task<IHttpActionResult> ExternalLoginCallback()
{
var content = await Request.Content.ReadAsStringAsync();
// I could see the content is a string with id_token, code , state etc.
//id_token is a JWT, so i can decode it and see the user claims and use them later
}
My Questions are :
Is Azure AD used for only authenticating the user ? What about authorizatoin ?
If i want to make calls to other APIs after authentication, how do i do that as i don't have access_token ?
I think i can exchange code with access_token but not sure which Azure endpoint i need to call to get access_token ?
What is the difference between AuthenticationMode.Active and AuthenticationMode.Passive ?
Azure AD can absolutely authorize a user and get your Access/Refresh tokens. It supports all oAuth 2.0 and OIDC flows.
You'll need to get an access token to make calls to an api. Let's say you want to call a /get endpoint on the MS Graph, you will stuff the access token into the body of the http request with the keyword Bearer ey... in front of it.
Additionally, you'll need to go into the Azure Portal and configure the delegated permissions you want to access.
The auth code is used to exchange for the access_token. I suggest checking out this protocol doc that shows you how to use all the endpoints. The short answer is you POST to the /token endpoint.
The difference between active and passive is a bit complex for a SO answer, I recommend reading this blog post about the differences.
I'll just add that if you want to see some sample code using Azure AD you can go to Azure AD Dev Guide or Azure AD code samples on Github.

Categories

Resources