Asp.Net Core 2.0 and Azure AD B2C for authentication on WebApp and API - asp.net-core

I have an existing small app that I use for test, it is in Asp.Net Core 1.1 for both the Web App and the API, the authentication is done using Azure AD B2C.
I am trying to move it to .Net Core 2.0 but I can't figure how to get it working, I tried using both sample from GitHub Azure-Samples for Web App and API, but I have either an unauthorized or 500 error when trying to access the api, if you have a working example for calling a web api from a web app using 2.0 and protected by AD B2C it will be greatly appreciated.
Edit:
The sample I use to test are :
Web App : WebApp-OpenIDConnect-DotNet core2.0
Web Api : B2C-WebApi core2.0
, I changed the appsettings values to match my b2c directory.
For my asp.net core 1.1 test app I use the same samples as above but from the master branch, with the same value for appsettings.
Edit 2
by default, in startup.cs I have this :
services.AddAuthentication()
.AddJwtBearer(option => new JwtBearerOptions
{
Authority = string.Format("https://login.microsoftonline.com/tfp/{0}/{1}/v2.0/",
Configuration["Authentication:AzureAd:Tenant"], Configuration["Authentication:AzureAd:Policy"]),
Audience = Configuration["Authentication:AzureAd:ClientId"],
Events = new JwtBearerEvents
{
OnAuthenticationFailed = AuthenticationFailed
}
});
which gives me the following error:
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:44352/api/values/5
Microsoft.AspNetCore.Server.Kestrel:Error: Connection id "0HL89JHF4VBLM", Request id "0HL89JHF4VBLM:00000001": An unhandled exception was thrown by the application.
System.InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found.
if modified services.AddAuthentication like that
services.AddAuthentication(sharedOption =>
{
sharedOption.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
the error is now
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler:Information: Failed to validate the token xxx.
Microsoft.IdentityModel.Tokens.SecurityTokenInvalidSignatureException: IDX10500: Signature validation failed. No security keys were provided to validate the signature.
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken)
at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.d__6.MoveNext()

I saw a pull request on the sample which correct this issue (Link), the services.AddAuthentication must be change to:
services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwtOptions =>
{
jwtOptions.Authority = $"https://login.microsoftonline.com/tfp/{Configuration["Authentication:AzureAd:Tenant"]}/{Configuration["Authentication:AzureAd:Policy"]}/v2.0/";
jwtOptions.Audience = Configuration["Authentication:AzureAd:ClientId"];
jwtOptions.Events = new JwtBearerEvents
{
OnAuthenticationFailed = AuthenticationFailed
};
});

I got this example working both for Core 1.1 and Core 2.0, please add the Oath Authentication as below,
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddAzureAdB2C(options => Configuration.Bind("Authentication:AzureAdB2C", options))
You configuration options will be defined inside of the class "AzureAdB2CAuthenticationBuilderExtensions", which is found inside of the
azure b2c project
Looks like your token is not being update it from the Azure, are you able to get the token from your web app? could you please verify that you are not getting null
Did you register your api scopes on your azure b2c tenant web app?
"ApiScopes": "https://fabrikamb2c.onmicrosoft.com/demoapi/demo.read"
you have to set scope in your web api and allows to be read on the web app, please follow click the link in order to set it up

Related

How to version an API such that one version of the api will require a bearer token and the other version does not

I have an asp.net core web api. Say if i want to have two versions of the api such that, version 1 will require a bearer token and version 2 will not require the barer token.
Since the token configuration code resides in the startup.cs file, how do I have two startup.cs files to match my requirement above? i am not even sure if it is legal to have two startup.cs files targeting two different versions of the api because the code to configure versioning of an asp.net core api will also reside in the startup.cs file.
Let me know what options are available to achieve my requirement above.
My current startup.cs file with token authentication enabled look like this..
public void ConfigureServices(IServiceCollection services)
{
services.Configure<AzureADSettings>(Configuration.GetSection("AzureAd"));
var azureADSettings = Configuration.GetSection("AzureAd").Get<AzureADSettings>();
var validAudience = new List<string>
{
azureADSettings.Audience
};
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
o.Authority = $"{azureADSettings.Instance}/{azureADSettings.TenantId}/";
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = true,
//azureADSettings.Audience
ValidAudiences = new List<string>(validAudience)
};
});
}
The Authorize middleware applies that check if you do not explicitly a controller or action as Anonymous. Maybe what you can do is:
Put the [Anonymous] attribute on top of your controller(s).
Mark your v1 API end-points in your controller(s) as [Authorize].
Leave the v2 API end-points as-is.
This way, the v2 API end-points should work fine with users not having a bearer token but v1 API end-points should expect a valid bearer token.

Error using OpenID Connect with .NET Core 3.0 ASP.NET Core API service

I'm trying to implement OpenID Connect with Azure AD in a .NET Core 3.0 ASP.NET Core API service.
It is an API service only, with no UI, and a separate SPA is used to access the API service.
Per the instructions here, I'm sending the sign-in request from my SPA, which, after authentication is being redirected back to the /signin-oidc endpoint on my API.
At this point, I get an error:-
Error from RemoteAuthentication: "Unable to unprotect the message.State.".
The initial request from the SPA looks like this:-
tenantId = my Azure AD tenant ID
clientId = my Azure AD application ID
responseType = "id_token"
redirectUri = "http://localhost:12345/signin-oidc"
scope = "openid"
responseMode = "form_post"
state = "12345"
nonce = "67890"
https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/authorize?client_id={clientId}&response_type={responseType}&redirect_uri={redirectUri}&scope={scope}&response_mode={responseMode}&state={state}&nonce={nonce}
And my API startup code looks like this:-
services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
.AddAzureAD(o =>
{
o.ClientId = "(tenant id)";
o.TenantId = "(client id)";
o.Instance = "https://login.microsoftonline.com/";
})
.AddAzureADBearer(o =>
{
o.ClientId = "(tenant id)";
o.TenantId = "(client id)";
o.Instance = "https://login.microsoftonline.com/";
});
If I omit the state parameter from the initial request, I get a different error:-
Error from RemoteAuthentication: "OpenIdConnectAuthenticationHandler: message.State is null or empty.".
The referenced instructions say that state is recommended, not required:-
A value included in the request that also will be returned in the token response. It can be a string of any content you want. A randomly generated unique value typically is used to prevent cross-site request forgery attacks. The state also is used to encode information about the user's state in the app before the authentication request occurred, such as the page or view the user was on.
However, the errors I'm getting seem to imply that state is required, and needs to be specially generated.
I've also tried with the following startup code, and get the same errors:-
services.AddAuthentication(options =>
{
options.DefaultScheme = Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.ClientId = "(client id)";
options.Authority = "https://login.microsoftonline.com/" + "(tenant id)";
});
The problem I think is that you can not send requests directly to /signin-oidc from your SPA application. Because the AddOpenIdConnect(...) handler requires that it is the one who makes the initial request to AzureAd and it that it also who receives the request back to /signin-oidc.
AddOpenIdConnect sets its own state value and passes is to the Identity provider aand it expects to see the same state value later when the browser calls /signin-oidc.

Identity Server 4 Client Credentials for custom endpoint on token Server

I implemented a token server using Identity Server 4.
I added a custom API endpoint to the token server and struggle with the authentication. The custom endpoint is inherited from ControllerBase and has 3 methods (GET, POST, DELETE).
I intend to call the custom endpoint from within another API using a dedicated client with credentials (server to server) implemented as HttpClient in .NET Core. There is no user involved into this.
For getting the access token I use the IdentityModel DiscoveryClient and TokenEndpoint.
So in sum I did the following so far:
setup "regular" identity server and validate it works -> it works
implement custom endpoint and test it without authorizatio -> it works
add another api resource ("api.auth") with a custom scope "api.auth.endpoint1"
setup a client with client credentials allowing access to scope "api.auth.endpoint1".
implement the HttpClient and test setup -> I get an access token via the Identity Model Token Endpoint.
Now, when I call the endpoint using the HttpClient with the access token I received I get response code 200 (OK) but the content is the login page of the identity server.
The documentation of Identity Server 4 state the use of
services.AddAuthentication()
.AddIdentityServerAuthentication("token", isAuth =>
{
isAuth.Authority = "base_address_of_identityserver";
isAuth.ApiName = "name_of_api";
});
as well as the use of
[Authorize(AuthenticationSchemes = "token")]
Unfortunatly the compiler state that .AddIdentityServerAuthentication can't be found. Do I miss a special nuget?
The nugets I use on the token server so far are:
IdentityServer4 (v2.2.0)
IdentityServer4.AspNetIdentity (v2.1.0)
IdentityServer4.EntityFramework (v2.1.1)
Figured out that part. The missing nuget for AddIdentityServerAuthentication is:
IdentityServer4.AccessTokenValidation
Struggling with the authorization based on the custom scope.
Does anyone know how the security has to be configured?
Configure a client with ClientGrantTypes = client_credentials and your api like this:
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5000";
options.ApiName = "api.auth";
});
Where ApiName is the name of the resource. Please note that resource != scope. In most samples the resource name is equal to the scope name. But not in your case, where resource name is api.auth and scope name is api.auth.endpoint1.
Configure the client to request the scope.
var tokenClient = new TokenClient(disco.TokenEndpoint, clientId, secret);
var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api.auth.endpoint1");
IdentityServer will lookup the Resource name and add that to the token as audience (aud) while the scope is added as claim with type scope.
This should be enough to make it work. Also check the sample project.
Custom authentication scheme and scope based policies for different access rights bundled together looks like that:
// Startup.ConfigureServices
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication("CustomAuthEndpointsAuthenticationScheme", options =>
{
options.Authority = "http://localhost:5000";
options.ApiName = "api.auth"; //IdentityServer4.Models.ApiResource.Name aka Audience
});
services.AddAuthorization(options =>
{
options.AddPolicy("Endpoint1Policy", policy => {
policy.AddAuthenticationSchemes(new string[] { "CustomAuthEndpointsAuthenticationScheme" });
policy.RequireScope("api.auth.endpoint1"); } ); //IdentityServer4.Models.Scope.Name
options.AddPolicy("Endpoint2Policy", policy => {
policy.AddAuthenticationSchemes(new string[] { "CustomAuthEndpointsAuthenticationScheme" });
policy.RequireScope("api.auth.endpoint2"); } ); //IdentityServer4.Models.Scope.Name
} );
// securing the custom endpoint controllers with different access rights
[Authorize(AuthenticationSchemes = "CustomAuthEndpointsAuthenticationScheme", Policy = "Endpoint1Policy")]
It seems not to interfere with the IdentityServer4 default endpoints nor with the ASP.NET Core Identity part.

Configute ASP Net Core 2 middleware for Bearer token authentication

I have a mobile app (built in xamarin) that uses Xamarin.Auth to let the user authenticate against Microsofts Oauth (https://login.live.com/oauth20_authorize.srf).
This works finns, and I retrieve an access_token back to my mobile app.
Now, I have created an ASP NET Core 2.0 Web API in order to serve as my application backend. I want this backend to validate that someone with a valid MS-token is comunicating with it.
I'm passing my token from the client with the header "Authorization" "Bearer XYZ....".
Now to my problem. I'm a bit confused about how to configure my Web API. In the ConfigureServices method, should I use "AddJwtBearer(..)" or "AddOpenIdConnect(..)" or "AddMicrosoftAccount(...)" ?
When Using Microsoft account, my webapi just redirects me to the MS login witch is NOT what I want. If no valid token is given, I want my backend to return "Unauthorized".
When Using the JwtBearer, my backend fails with the message "No SecurityTokenValidator available for token".
My config for jwt:
services.AddAuthentication(options =>
{
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwtOptions =>
{
jwtOptions.Authority = "https://login.live.com";
jwtOptions.Audience = "myClientId";
jwtOptions.RequireHttpsMetadata = false;
});
and in Configure, I have app.UseAuthentication() Before app.UseMvc()
Where am I going wrong?
Best regards Andreas

SPA (Aurelia) + ASP.NET Core WebAPI + Google Authentication

My SPA application (using Aurelia) calls my ASP.NET Core 2 Web API. I need to authenticate users with Google OIDC provider and also secure the Web API with the same method.
Currently I'm able to authenticate user on the client (SPA) side and retrieve id token and access token. With each API call I send the access token in the header.
Now I'm not sure how to handle the server side to validate the token and grant or deny the access to the API. I followed official docs how to add external login providers, but it seem to work only for server-side MVC applications.
Is there any easy way how to do this?
I think for instance IdentityServer4 can support this scenario, but it seems to me too complex for what I need to do. I don't need my own identity/authorization server after all.
Update:
Based on Miroslav Popovic answer, my configuration for ASP.NET Core 2.0 looks like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(o =>
{
o.Authority = "https://accounts.google.com";
o.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = "accounts.google.com",
ValidAudience = "xxxxxxxxxxxxx.apps.googleusercontent.com",
ValidateAudience = true,
ValidateIssuer = true
};
});
services.AddMvc();
}
And in Configure() I call app.UseAuthentication().
When using this setup I get failure message No SecurityTokenValidator available for token.
Update 2:
I made it work. The server configuration is correct. The problem was I was sending access_token to the API instead of id_token.
Since you already have the access token, it shouldn't be too hard to use it to add authentication. You would need something along these lines (not tested):
// Inside Startup.cs, ConfigureServices method
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(
options =>
{
var tokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = "accounts.google.com",
ValidateAudience = false
};
options.MetadataAddress = "https://accounts.google.com/.well-known/openid-configuration";
options.TokenValidationParameters = tokenValidationParameters;
});
// Inside Startup.cs, Configure method
app.UseAuthentication(); // Before MVC middleware
app.UseMvc();
// And of course, on your controllers:
[Authorize]
public class MyApiController : Controller
This post from Paul Rowe might help some more, but note that it's written for ASP.NET Core 1.x and authentication APIs changed a bit in 2.0.
There is also a lot of info here on SO, like this question.