0

I have a .net core 3.1 website which uses Active Directory for authentication. I can sign in with a user I have created in the Azure portal.

I then added an API controller.

I have managed to get a token using the following code:

public static async Task<ADAuthResponse> GetAuthToken()
    {
        using HttpClient httpClient = new HttpClient();

        StringContent body = new StringContent("client_id=6865ee8xxxxxxx7-9f28-867ff93b079c&scope=user.read%20openid%20profile%20offline_access&username=cardiffwebjob@xxxxxx.onmicrosoft.com&password=!!XXXXX!123&grant_type=password&client_secret=jJg.6mXXXXXXXXX-w-3l9SHv-T", Encoding.UTF8, "application/x-www-form-urlencoded");

        HttpResponseMessage response = await httpClient.PostAsync("https://login.microsoftonline.com/62580128-946f-467b-ae83-7924e7e4fb18/oauth2/v2.0/token", body);

        ADAuthResponse result = await JsonSerializer.DeserializeAsync<ADAuthResponse>(await response.Content.ReadAsStreamAsync());

        return result;
    }

I have then tried to call the endpoint with this code:

public static async Task UpdateWebJobStatus(UpdateFunctionValues updateFunctionValues)
    {
        // get the auth token
        ADAuthResponse authResponse = await GetAuthToken();

        using HttpClient httpClient = new HttpClient();
        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResponse.access_token);

        StringContent stringContent = new StringContent(JsonSerializer.Serialize(updateFunctionValues), Encoding.UTF8, "application/json");

        HttpResponseMessage httpRequestMessage = await httpClient.PostAsync("https://cardiffwebsite.azurewebsites.net/api/DashboardAPI/SetFunctionStatus", stringContent);
    }

And the controller in the website looks like this:

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[ServiceFilter(typeof(IExceptionFilter))]
[ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)]
[Route("api/[controller]")]
[ApiController]
public class DashboardAPIController : ControllerBase
{
    private readonly BaseContext _db;
    private readonly IHubContext<WebJobStatusHub> _hub;
    private readonly IHttpContextAccessor _httpContextAccessor;

    public DashboardAPIController(BaseContext db, IHubContext<WebJobStatusHub> hubContext, IHttpContextAccessor httpContextAccessor)
    {
        _db = db;
        _hub = hubContext;
        _httpContextAccessor = httpContextAccessor;
    }

    [HttpPost]
    [Route("SetFunctionStatus")]
    public async Task SetFunctionStatus([FromBody] UpdateFunctionValues updateFunctionValues)
    {
        WebJobStatusHub hub = new WebJobStatusHub(_db, _hub);
        await hub.SendJobStatusUpdate(updateFunctionValues.WebJobId, updateFunctionValues.FunctionId, updateFunctionValues);
    }
}

The startup.cs in the website relating to authentication looks like this:

services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
            .AddAzureAD(options => _config.Bind("AzureAd", options))
            .AddJwtBearer(options =>{
                options.Authority = "https://login.microsoftonline.com/62580128-946f-467b-ae83-7924e7e4fb18/";
                options.Audience = "f8954991-11xxxxx-73769d3c98cd";
                options.TokenValidationParameters.ValidateLifetime = true;
            options.TokenValidationParameters.ClockSkew = TimeSpan.Zero;
            });

I am getting this error when calling the API:

HTTP/1.1 401 Unauthorized Server: Microsoft-IIS/10.0 WWW-Authenticate: Bearer error="invalid_token", error_description="The signature is invalid"

I have read about 100 threads about how to fix/configure Azure and/or my app to get this to work but with no luck.

Can anyone give me any pointers please? It maybe something I have/have not done correctly in Azure or it could be the way I have re-configured authentication in my startup. I just cannot find the problem.

Any pointers/help would be greatly appreciated.

in response to the comment here is what my app registration looks like:

enter image description here

in response to people helping me in the Azure configuration about exposing an API... i don't appear to have done anything here.enter image description here

Trevor Daniel
  • 3,785
  • 12
  • 53
  • 89
  • Have you exposed your API to the client in your app registrations? – Purushothaman Sep 05 '20 at 15:44
  • This screen shot you added is API permission this gives the graph client to read the details based on the permission. Can you confirm?, you added your client app registration Id under "Expose an API" – Purushothaman Sep 05 '20 at 16:08
  • @Trevor Daniel - What would happen if you use IdentityClient lib to generate access token - https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/wiki/Acquiring-tokens-with-username-and-password – user1672994 Sep 05 '20 at 18:25
  • 2
    I wonder why don't you use Microsoft.Identity.Web. Here's a very good code sample: https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/master/4-WebApp-your-API/4-1-MyOrg – derisen Sep 05 '20 at 18:49
  • @Purushothaman I have updated my question based on your response. Again. many thanks! – Trevor Daniel Sep 05 '20 at 20:08
  • You need to add a scope and in your client app registration add permission to this api inorder to build a trust between your client and API. Please follow this article [Secure .Net Core 3 API](https://cmatskas.com/create-a-protected-api-that-calls-in-ms-graph-on-behalf-of-a-power-app/) – Purushothaman Sep 05 '20 at 20:41
  • Pls check Auth implementation of Web App here - https://learn.microsoft.com/en-us/samples/azure-samples/active-directory-b2c-dotnetcore-webapp/an-aspnet-core-web-app-with-azure-ad-b2c/ and Web API here - https://github.com/Azure-Samples/active-directory-b2c-dotnetcore-webapi. There seems to be an issue with the code setup for auth. – Harshita Singh Sep 06 '20 at 08:45
  • Obviously, the way you get the token is wrong. You cannot use the wrong token to call the api. Please change "scope" to be consistent with the expected token receiver. – Carl Zhao Sep 07 '20 at 01:42

1 Answers1

1

Got it working... not sure it's 100% correct but this is what i did

Firstly setup an "App Registration" in Azure and took a note of the client id and secret.

Then, in the startup of my website i updated startup.cs to look like this:

services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
            .AddAzureAD(options => _config.Bind("AzureAd", options))
            .AddJwtBearer(options =>
            {
                options.Authority = "https://login.microsoftonline.com/TenantIdHere";
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    ValidateIssuerSigningKey = false,
                    ValidateLifetime = false,
                    ValidateActor = false,
                    ValidateTokenReplay = false
                };
            });

And decorated the api controller like this:

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Authorize(AuthenticationSchemes = AzureADDefaults.AuthenticationScheme)]
[Route("api/[controller]")]
[ApiController]

And i get the auth token like this:

public static async Task<ADAuthResponse> GetAuthToken()
    {
        using HttpClient httpClient = new HttpClient();

        // client id and secret from the app registration
        byte[] authHeader = Encoding.UTF8.GetBytes("ClientIdHere" + ":" + "ClientSecretHere");
        string base64 = Convert.ToBase64String(authHeader);

        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64);

        StringContent body = new StringContent("grant_type=client_credentials&scope=", Encoding.UTF8, "application/x-www-form-urlencoded");

        HttpResponseMessage response = await httpClient.PostAsync("https://login.microsoftonline.com/TenantIdHere/oauth2/token", body);

        ADAuthResponse result = await JsonSerializer.DeserializeAsync<ADAuthResponse>(await response.Content.ReadAsStreamAsync());

        return result;
    }
Trevor Daniel
  • 3,785
  • 12
  • 53
  • 89