Blazor Azure AD Autentication with custom web Api

Hello

i try to create an application that use REST Endpoint authenticate with Azure AD.
I create a Radzen Aplication and configure the Rest Data Service to use Azure Ad Auth and fetch data from a .net Core web api project.
I Open the solution in VS2019 and i see in startup.cs this configuration

services.AddAuthentication()
                .AddCookie("TestRestSignInScheme")
                .AddOpenIdConnect("TestRest", options =>
                {
                    options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
                    options.TokenValidationParameters.ValidateIssuer = false;
                    options.Authority = Configuration["TestRest:Authority"];
                    options.ClientId = Configuration["TestRest:ClientId"];
                    options.ClientSecret = Configuration["TestRest:ClientSecret"];
                    options.CallbackPath = new PathString("/test-rest-callback");
                    options.SaveTokens = true;
                    options.SignInScheme = "TestRestSignInScheme";
                });

in the web api project i set this configuration

        services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
                .AddAzureADBearer(options => Configuration.Bind("AzureAd", options));

But when i call the endpoint i have the 401 unauthorized response....

Is the web api configuration correct?

Hi @Knuckles,

Unfortunately we can't tell if the configuration is correct or not. Does it work with other applications?

Hello

yes in other (custom) application with this configuration in blazorApp startup worked

services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));

        services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
        {
            options.ResponseType = "code";
            options.SaveTokens = true;

            //options.Scope.Add("offline_access");
            //options.Scope.Add("User.Read");
            options.Resource = "{clientID}";
            options.ClientSecret = "{clientSecret}";

        });

I see that when the code try to retrive the token

var accessToken = await contextAccessor.HttpContext.GetTokenAsync("TestRest", "access_token");

i have this result

PAQABAAAAAAAm-06blBE1TpVMil8KPQ41ARLOejoQg7gqL67ohJwID-Y1vEGTeU4EEFCui08aki3NbiPOHe-GMHDt3iXTpE0DGovL2RbBA-0kWdD4xxGs4wJ2-s3lPf-yo2M8K5VUXKcsCignlGj7x74vKQrDo_u3zbbJUG5zoc5K2cw4d2rkizHVBLuNZKKqM-654IWDpPWthWLPbGrCPCqQXJnOF0wCfBkFDEb7VSd0nVReNFoGVAYIGE5KgqFQ3WqMpRCqLC4-C-3ZO1I4R7CIDwfBNmB03cOvy4C_-WqaDhVdi5PGDMagQ3WDEdVVzKN-TM4Uwaw4hOidV9xnWrK-NTxcsDTG00NOhotn5i-GDveavdHVAbJrIvJyAwkwxbN88jz5DB-2DDJQW5gRT5ufZl9Th6tZXq2IGpF6eTGIvXDQYBng1D9Sg55Jl_Wt4IT1mRiLH2vlJ-A3cDCaBBJj3-bvu6Bke_GX9MBkdFdH-0lDKhmiHV7pZQD_c3CMtO1KXHktEs5a208mFW-rjSva-DG9laNVx8srdfq-74kjCplUQzbYC13S9-d7L9FB2_3jop95zCADPlwDqz2MuN60DhCJviFn_7USBoQmIcC4pNZalJNckOys3_BLZLNeco6I5gu9PN8gAA

and i think that is not a JWT token right?

You can try using the same configuration then. Here is how to extend the Startup.cs file.

hello

i try to extend the configuration but i have an error when i try to start the application.

OptionsValidationException: The 'Instance' option must be provided.

in the Startup.Custom.cs i have :

partial void OnConfigureServices(IServiceCollection services)
{
services.AddDevExpressBlazor();

        services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
        .AddAzureAD(options =>  Configuration.Bind("AzureAd", options));

        services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
        {
            options.ResponseType = "code";
            options.SaveTokens = true;

            //options.Scope.Add("offline_access");
            //options.Scope.Add("User.Read");
            options.Resource = "{clienId}";
            options.ClientSecret = "{clientSecret}";

        });
        services.AddTransient<WeatherForecastService>();
    }

and this is the appsetting.json

"TestRest": {
"ClientId": "{clientID}",
"ClientSecret": "{clientSecret}",
"Authority": "https://login.microsoftonline.com/{tenantID}",
"Instance": "https://login.microsoftonline.com/",
"Domain": "{domain}",
"TenantId": "{tenantID}"
},
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "{domain}",
"TenantId": "{tenantID}",
"ClientId": "{clientID}"
}

but the "Instance" is provided in configuration....as I wrote earlier the "token" retrive is not a JWT token right? what is expected to extract when use the contextAccessor.HttpContext.GetTokenAsync("TestRest", "access_token"); ?

what is expected to extract when use the contextAccessor.HttpContext.GetTokenAsync("TestRest", "access_token"); ?

The access_token which is returned upon successful authorization.

ok i have this token returned..but is not a jwt token

Yes, the access_token is not a JWT token.

ok thanks...so what is it? if i know what is it i try to configure my webapi solution...

The access_token is returned by Azure AD itself - it should be JWT as per their documentation. I can't tell why your API returns an access_token which is not JWT.

By the way you said that your custom application works. How do you get the access_token there?

i have this code in _Host.cshtml

 @{
        var tokens = new InitialApplicationState
        {
            AccessToken = await HttpContext.GetTokenAsync("access_token"),
            RefreshToken = await HttpContext.GetTokenAsync("refresh_token")
        };
    }

and this configuration

services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));

        services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
        {
            options.ResponseType = "code";
            options.SaveTokens = true;

            //options.Scope.Add("offline_access");
            //options.Scope.Add("User.Read");
            options.Resource = "{clientId}";
            options.ClientSecret = "{clientSecret}";

        });

whit this configuration

"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "{domain}",
"TenantId": "{tenantId}",
"ClientId": "{clientId}"
}

The code that Radzen generates uses an overload of exact same method:

HttpContext.GetTokenAsync("TestRest", "access_token");

Unfortunately I am out of any further suggestions.

I think yours is an auth code due to your reponseType = "code" I believe this must get posted to /token endpoint to get and access_token with the code grant flow.

OpenIdConnectResponseType.CodeIdToken is "code id_token" and is similar to your code.

You can try customizing the generated code in Startup.cs via the code generation ignore list.

And maybe try AcquireTokenByAuthorizationCode method

Yes you are right.
it returned the authorization_code and now with AcquireTokenByAuthorizationCode i can obtain the id_token.
but when i try to get from HttpContext.GetTokenAsync("TestRest", "access_token");
i have null value

You could try the client_credentials flow with AcquireTokenForClientAsync() instead of code grant like so:

HTH,
Josh