How to register Authentication Provider for identity in ASP.NET Core 3.1 using a custom service? - asp.net-core

I have an ASP.NET Core 3.1 app that uses Identity to authenticate users. I would like to configure Identity to allow external logins from different providers like Facebook and Twitter.
I have multiple external-login-provider stored in a database. These records can be accessed using IAuthProvider service.
When the app is configured using the ConfigureServices() method, I want to resolve an instance of IAuthProvider to get all available records from the database and then add the needed login-providers.
Below is my code I am struggling on how to resolve IAuthProvider instance while in the ConfigureServices() method. Perhaps there is a better way to configure or defer configure the providers until later but not sure how and where to configure the server.
How can I create an instance of the IAuthProvider and correctly configure the Identity providers?
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>(options =>
{
options.UseMySql(Configuration.GetConnectionString("MySql"));
});
services.AddScoped<IAuthProvider, AuthProvider>();
// other services
var providers = // Here I somehow need to resolve an instance of IAuthProvider;
AuthenticationBuilder authBuilder = services.AddAuthentication();
foreach (var provider in providers.All())
{
if (provider.Name == ExternalLoginProvider.Facebook)
{
authBuilder.AddFacebook(options =>
{
options.AppId = provider.AppId;
options.AppSecret = provider.Secret;
});
}
if (provider.Name == ExternalLoginProvider.Twitter)
{
authBuilder.AddTwitter(options =>
{
options.ConsumerKey = provider.AppId;
options.ConsumerSecret = provider.Secret;
});
}
// Other providers as needed per the records found in the database
}
}

I see your code has already registered the implementation of IAuthProvider, you can simpply call servcice.GetService() to get an instance of AuthProvider.
I don't get your point of having different authprovider in such a way, according to this document you can add different auth provider in chain like this:
services.AddAuthentication()
.AddMicrosoftAccount(microsoftOptions => { ... })
.AddGoogle(googleOptions => { ... })
.AddTwitter(twitterOptions => { ... })
.AddFacebook(facebookOptions => { ... });
Any reason why adding them seperatly?
Edit:
If you just want to get the instance of IAuthProvider, just try
var serviceProvider = services.BuildServiceProvider();
var provider = serviceProvider.GetService<IAuthProvider>();

Related

self hosted ASP.NET core Web API with Kestrel using HTTPS

I'm attempting to convert an old WCF service to an ASP.NET Core Web API, making use of the CoreWCF package. A key feature of this existing service is that it's being self hosted by an other application and is able to gracefully start & stop by button presses done by the users on a WPF UI.
This service should be accessed via an HTTPS endpoint and this is where I'm struggling. I have been able to succesfully access the API using regular HTTP, but I'm guessing I'm missing something or it's got something to do with my certificates. I've tried both using the default certificate by passing the listeningOption.UseHTTPS() but when I go to the URL, it'll return an error 500. I've also tried a self-signed certificate and passing it in the afformentioned method, but it keeps returning the same error.
The code for configuring everything looks like this:
Configuring the webhost:
private void CreateWebHostBuilder(){
host = WebHost.CreateDefaultBuilder()
.UseKestrel(options =>
{
options.AllowSynchronousIO = true;
options.ListenLocalhost(Startup.PORT_NR,
lOptions => lOptions.UseHttps("{absolute path}", "{password}"));
);
})
.ConfigureLogging(logging => { logging.SetMinimumLevel(LogLevel.Warning); })
.UseSetting(WebHostDefaults.DetailedErrorsKey, "true")
.UseShutdownTimeout(TimeSpan.FromSeconds(1))
.UseStartup<Startup>()
.Build();
}
Inside the Startup class:
Configuring the IApplicationBuilder:
public void Configure(IApplicationBuilder app){
app.UseServiceModel(builder =>
{
// Add the Echo Service
builder.AddService<EchoService>()
// Add service web endpoint
.AddServiceWebEndpoint<EchoService, IEchoService>(
WEB_API_PATH,behavior => { behavior.HelpEnabled = true;}
);
});
app.UseMiddleware<SwaggerMiddleware>();
app.UseSwaggerUI();
app.UseAuthentication();
app.UseHttpsRedirection();
}
Configuring the services:
public void ConfigureServices(IServiceCollection services){
services.AddServiceModelWebServices()
.AddHostedService<EchoService>()
.AddSingleton(new SwaggerOptions())
.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate();
services.AddHttpsRedirection(options =>
{
options.HttpsPort = HTTPS_PORT_NR;
});
}
Since this started as a WPF application, I don't have an appsettings.json file to add, and I wouldn't really know what to add in it if this were needed.
So if anyone has any suggestions or tutorial reference, be sure to let me know, any help is welcome.
So I eventually figured out what was missing. I simply needed to add another endpoint, that would listen to https requests like so:
public void Configure(IApplicationBuilder app){
app.UseServiceModel(builder =>
{
// Add the Echo Service
builder.AddService<EchoService>()
// Add service web endpoint
.AddServiceWebEndpoint<EchoService, IEchoService>(
WEB_API_PATH,behavior => { behavior.HelpEnabled = true;}
);
.AddServiceWebEndpoint<EchoService, IEchoService>(new WebHttpBinding
{
Security = new WebHttpSecurity { Mode = WebHttpSecurityMode.Transport },
WEB_API_PATH,behavior => { behavior.HelpEnabled = true;}
);
});
// remainder of code.
}

OpenIdDict Degraded Mode with ClientCredentials flow

I'm following this blog about allowing OpenIDDict to wrap an alternative authentication provider but return a JWT token from OpenIDDict itself:
https://kevinchalet.com/2020/02/18/creating-an-openid-connect-server-proxy-with-openiddict-3-0-s-degraded-mode/
This is really about intercepting the Authorization Code flow rather than the Client Credentials flow, but it provides a good starting point.
Unfortunately it states that "we don't need to override the HandleTokenRequestContext", which is appropriate for the blog but not (as far as I know) for my use case.
I think I need to implement a custom HandleTokenRequestContext but when I do so, the code runs, no errors but the HTTP response is empty. No token is generated.
How should I properly intercept the Client Credentials flow so that I can call out to another provider to validate the credentials, get a result and include that in the custom claims that I need to add to the JWT?
Code below:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DbContext>(options =>
{
// Configure the context to use an in-memory store - probably not needed?
options.UseInMemoryDatabase(nameof(DbContext));
// Register the entity sets needed by OpenIddict.
options.UseOpenIddict();
});
services.AddOpenIddict()
.AddCore(options =>
{
options.UseEntityFrameworkCore()
.UseDbContext<DbContext>();
})
.AddServer(options =>
{
options.SetTokenEndpointUris("/connect/token");
options
//.AllowRefreshTokenFlow()
.AllowClientCredentialsFlow();
// Register the signing and encryption credentials.
// options.AddDevelopmentEncryptionCertificate()
// .AddDevelopmentSigningCertificate();
//Development only
options
.AddEphemeralEncryptionKey()
.AddEphemeralSigningKey()
.DisableAccessTokenEncryption();
// Register scopes (i.e. the modes we can operate in - there may be a better way to do this (different endpoints?)
options.RegisterScopes("normal", "registration");
//TODO: Include Quartz for cleaning up old tokens
options.UseAspNetCore()
.EnableTokenEndpointPassthrough();
options.EnableDegradedMode(); //Activates our custom handlers as the only authentication mechansim, otherwise the workflow attempt to invoke our handler *after* the default ones have already failed
//the request
options.AddEventHandler<ValidateTokenRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
//TODO: Check that the client Id is known
if (!string.Equals(context.ClientId, "client-1", StringComparison.Ordinal))
{
context.Reject(
error: Errors.InvalidClient,
description: "The specified 'client_id' doesn't match a known Client ID.");
return default;
}
return default;
}));
options.AddEventHandler<HandleTokenRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
var identity = new ClaimsIdentity(TokenValidationParameters.DefaultAuthenticationType, OpenIddictConstants.Claims.Name, OpenIddictConstants.Claims.Role);
identity.AddClaim(OpenIddictConstants.Claims.Subject, context.ClientId, OpenIddictConstants.Destinations.AccessToken, OpenIddictConstants.Destinations.IdentityToken);
if (context.Request.Scope == "registration")
{
//TODO: Authenticate against BackOffice system to get it's token so we can add it as a claim
identity.AddClaim("backoffice_token", Guid.NewGuid().ToString(), OpenIddictConstants.Destinations.AccessToken);
}
else
{
//TODO: Authenticate against internal authentication database as normal
}
var cp = new ClaimsPrincipal(identity);
cp.SetScopes(context.Request.GetScopes());
context.Principal = cp;
//This doesn't work either
//context.SignIn(context.Principal);
//ERROR: When this exits the response is empty
return default;
}));
});
//.AddValidation(options =>
//{
// options.UseLocalServer();
// options.UseAspNetCore();
//});
services.AddControllers();
services.AddHostedService<CredentialLoader>();
}
In the end, I got this working with 3 changes:
In the HandleTokenRequestContext:
var identity = new ClaimsIdentity(TokenValidationParameters.DefaultAuthenticationType);
Not sure if this is essential or not. Then remove the
context.Principal = cp;
but re-add the
context.SignIn(context.Principal);

.Net 5 Web API Authorization Server OpenIdDict with Custom DB Store (NO EF)

I am wokring on a .NET 5 POC with custom storage provider and adding authorization server with OpenIDDict. I am able to get the custom store implementation working but I am unable to wire up the authorization server to use the custom store implementation as documentation exists only for EntityFramework implementation.
Here is my startup configurationservices snippet
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IUserStore<ApplicationUser>, UserStore>();
services.AddTransient<IRoleStore<ApplicationRole>, RoleStore>();
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddDefaultTokenProviders();
string connectionString = Configuration.GetConnectionString("DefaultConnection");
services.AddTransient<SqlConnection>(e => new SqlConnection(connectionString));
services.AddOpenIddict()
.AddServer(options =>
{
options.AllowClientCredentialsFlow();
options.SetTokenEndpointUris("connect/token");
// Encryption and signing of tokens
options
.AddEphemeralEncryptionKey()
.AddEphemeralSigningKey();
// Register scopes (permissions)
options.RegisterScopes("api");
options.UseAspNetCore().EnableTokenEndpointPassthrough();
});
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "ServerAPI", Version = "v1" });
});
}
typical EF implementation looks like this
services.AddDbContext<DbContext>(options =>
{
// Configure the context to use an in-memory store.
options.UseInMemoryDatabase(nameof(DbContext));
// Register the entity sets needed by OpenIddict.
options.UseOpenIddict();
});
any pointers on what custom store implementation should be?
Also the documentation provided here for custom authorization server seems to be outdated and not maintained anymore https://kevinchalet.com/2016/07/13/creating-your-own-openid-connect-server-with-asos-creating-your-own-authorization-provider/ as well.

ASP.Net Core Identity with JwtBearer AuthenticationScheme map claims to context User object

I have a React Front end using the msal lib to authenticate the user client side with our Azure AD. This works great and authentication has no issues. I also have an ASP.Net Core WebApi to provide data to the client. I am using the JwtTokens to pass the Bearer token in the request. The WebApi is able to validate the token and all is well... I thought, however, when the WebApi method is invoked the only way I can get the User's email or name is to query the User.Claims with Linq.
this.User.Claims.Where(c=> c.Type == "preferred_username").FirstOrDefault().Value
I was about to go down the road of mapping these linq queries to an object which could be injected into the WebApi's controller, but that seems wrong.
I am obviously missing something in my Startup.cs for the WebApi, Any help or suggestions would be great!:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
//add authentication JwtBearer Scheme
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Audience = Configuration["JwtSettings:Audience"];
options.Authority = Configuration["JwtSettings:Authority"];
options.Events = new JwtBearerEvents
{
OnTokenValidated = ctx =>
{
//log
return Task.CompletedTask;
},
OnAuthenticationFailed = ctx =>
{
//log
return Task.CompletedTask;
}
};
options.SaveToken = true;
});
services.AddAuthorization();
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}

How to set deaful Authorization in .net core 2

I am using a custom requirement like this:
services.AddAuthorization(options => {
options.AddPolicy("MyAuthorizationRequirement",policy => {
policy.Requirements.Add(new MyRequirement());
});
});
I want to be able to set an [Authorize] attribute on my controller and use a custom authorization method.
but with the custom requirement i set i have to use [Authrization("MyAuthorizationRequirement")]
how can I set some sort of default to use just a single attribute ?
Let me try to explain what is required here.
What services.AddAuthorization does is, it tries to configure authorization. Before configuring authorization first you need to configure authentication. For that in the ConfigureServices method, you can do something like this.
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Audience = "http://localhost:5001/";
options.Authority = "http://localhost:5000/";
});
Now you can configure,
services
.AddAuthorization(options =>
{
options.AddPolicy("PolicyName", policy => policy.RequireClaim("SomeClaimName"));
// you can configure your requirement above in various ways, you might want to check those out
});
And finally on the Configure method,
app.UseAuthentication();
So now in the controller,
// [Authorize("PolicyName")]
// If you have above in your controllers/actions, those will only be accessible to users who has "SomeClaimName"
// [Authorize]
// Those who are authenticated, can access these endpoints
You can create a new class which inherit from AuthorizeAttribute
public class MyAuthorizationAttribute : AuthorizeAttribute
{
public MyAuthorizationAttribute()
: base("MyAuthorizationRequirement")
{
}
}
and use [MyAuthorization] on your controllers.