3

I have limited experience on Azure AD and authentication mechanism. So far I cannot figure out why is not working. Here is the scenario:

I have an ASP net core 2.1 application deployed in azure web app service. For authentication I’m using Open ID connect with .AddOpenIdConnect and provide client_id, secret_id, etc. When users are accessing my web API they are redirected to Microsoft login.

Now I need to expose an API to a third party application (scheduled web job) which is not in Azure.

I tried to use this sample from Microsoft, only the console app, as I already have the WebApp in Azure. Running the sample I’m able to get the token, but when I call my API the response is the HTML to Microsoft login page.

On Azure portal on

Enterprise Application -> daemon-console -> Activity -> Service Principal sign-ins

I can see the success sign in.

Note: for testing I run the web app on my local machine and from the console application I’m calling API https://localhost:44306/api/test.

Asp .net core app:

services.AddAuthentication(option =>
{
    option.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    option.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    option.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(option =>
{
    option.Cookie.Name = "myWebApp"; 
    option.Cookie.SecurePolicy = CookieSecurePolicy.Always;
    option.Cookie.SameSite = SameSiteMode.None;
})
.AddOpenIdConnect(option =>
{
    option.ClientId = client_id;
    option.ClientSecret = client_secret;
    option.Authority = authority;
    option.SignedOutRedirectUri = "http://localhost:44306/";
    option.CorrelationCookie.Name = "myWebAppCorrelation"; 
    option.CorrelationCookie.SecurePolicy = CookieSecurePolicy.Always;
    option.NonceCookie.Name = "WebAppNonce";
    option.NonceCookie.SecurePolicy = CookieSecurePolicy.Always;
    option.Resource = "https://graph.windows.net";
    option.ResponseType = "id_token code";
})

Console app trying to access the API ( code extracted from Microsoft sample )

app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
    .WithClientSecret(config.ClientSecret)
    .WithAuthority(new Uri(config.Authority))
    .Build();
    
result = await app.AcquireTokenForClient(scopes).ExecuteAsync(); // ok

var httpClient = new HttpClient();
var defaultRequestHeaders = httpClient.DefaultRequestHeaders;
if (defaultRequestHeaders.Accept == null || !defaultRequestHeaders.Accept.Any(m => m.MediaType == "application/json"))
{
    httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
defaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);

HttpResponseMessage response = await httpClient.GetAsync(webApiUrl);
if (response.IsSuccessStatusCode) // ok 
{
    string json = await response.Content.ReadAsStringAsync(); // here I'm getting the HTML to login page
    var result = JsonConvert.DeserializeObject<List<JObject>>(json);
    Console.ForegroundColor = ConsoleColor.Gray;
    processResult(result);
}

The only difference between the sample code and my scenario is that the web app from sample is using services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApi(Configuration) but I cannot use .AddMicrosoftIdentityWebApi in Asp .Net core 2.1

Does anyone has an idea where the issue might be ? Do I need to add another authentication scheme ?

7
  • Why do you still need to log in when using the daemon? Which authentication flow are you currently using? Commented Mar 31, 2021 at 1:41
  • @CarlZhao, if I understand correctly, my web app is using implicit grant flow. I don't want to log in using daemon. I just need to expose an API from my web app for other application which is not in Azure. I was expecting once I got the token I will be able to call the the API, but instead the response from calling that API is the login page. Commented Mar 31, 2021 at 7:27
  • From your description. Is the web api you are calling not in Azure? However, the web api called in the sample you provided is exposed in Azure. Commented Mar 31, 2021 at 7:39
  • @CarlZhao, For testing this scenario I have both web API and console app on my local environment. I updated the description and added some code samples. Commented Mar 31, 2021 at 7:57
  • It's just a guess of mine, what will you get if you try to use the client credential flow? Commented Mar 31, 2021 at 8:10

1 Answer 1

1
+50

You need to support JWT authentication in addition to cookie authentication. So you need to add AddJwtBearer. You also need to extend the authentication check because you are now supporting multiple schemes.

This is how I would do it:

// public void ConfigureServices(IServiceCollection services)
services.AddAuthentication(option =>
{
    option.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    option.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    option.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, cfg => {
    cfg.Authority = authority;
    cfg.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
    {
        ValidAudience = /*see scopes of your deamon app*/
    };
})
.AddCookie(option =>
{
    // ...
})
.AddOpenIdConnect(option =>
{
    // ...
})


// public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

app.UseAuthentication();
// https://github.com/aspnet/Security/issues/1847
app.Use(async (context, next) =>
{
    if (!context.User.Identity.IsAuthenticated)
    {
        var result = await context.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme);
        if (result.Succeeded)
        {
            context.User = result.Principal;
        }
    }
    await next();
});
app.UseAuthorization();


Sign up to request clarification or add additional context in comments.

2 Comments

Thank you Alex. It worked by adding AddJwtBearer. However, I didn't add the middleware that perform context.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme);. Instead, I added [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] on the specific API controller that I want to allow external app access. In this way only that API will use JWTBearer scheme. In this way, if I understand correctly, the rest of APIs are working with the default scheme, and only the specified one can be accessed by the daemon-app.
If you only want this to apply to certain controllers/routes, using the attribute is better than my suggestion. Thanks for sharing.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.