I currently have two different projects, one the MainAPI, that will expose the functions for my web client, and another one, the AuthAPI, which handles all the the auth requests, all built using netcore 2.
If I call the AuthAPI directly, it will handle requests as desired.
When I try to have the MainAPI requests be authenticated by the AuthAPI, it fails to do so, although I can see the requests comming in and out the AuthAPI.
Here is my services authentication configuration in MainAPI Startup.cs:
services.AddAuthentication(option =>
option.DefaultAuthenticateScheme = "Bearer")
.AddJwtBearer(options =>
{
options.MetadataAddress = "http://localhost:5019/auth/api/info";
options.Authority = "http://localhost:5019";
options.Audience = AUDIENCE;
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.BackchannelHttpHandler = new BackChannelHttpHandler();
options.IncludeErrorDetails = true;
});
Here is BackChannelHttpHandler class:
public class BackChannelHttpHandler : HttpMessageHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpClient client = new HttpClient(new HttpClientHandler(), disposeHandler: false);
client.DefaultRequestHeaders.Add("Accept", "application/json");
var res = await client.GetAsync(request.RequestUri);
return res;
}
}
And the controller I am calling as the Authorize annotation as follows:
[Authorize(AuthenticationSchemes = "Bearer")]
As for the AuthAPI, I have Cors configured in Startup.cs:
services.AddCors(options =>
{
options.AddPolicy("CorsConfig",
builder => builder
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
.Build());
});
In AuthAPI controller I have also the [EnableCors("CorsConfig")] Annotation.
I can get the call to the AuthAPI controller, and it returns a HTTP-200, but in my MainAPI something in the authorization process gets called that Unauthorizes it and my call to the MainAPI controller never gets executed.
My questio is, what am I doing wrong in the MainAPI authentication process, that authenticates by itself the token, and invalidates the request.
pay attention for this parameter options.Audience;
it should have available end-points.
or you can share a key between applications for more productivity. Each end-point can read payload from token, but only auth-server can produce tokens.
Related
I'm using AspNetCore 2 with HTTP.SYS. I'm trying to implement an authentication scheme that
1. Performs custom (stateless) authentication if a request has a given header, or
2. If not, defaults back to windows auth
My initial attempt was adding a policy scheme that selects the appropriate authentication scheme based on the request. This doesn't quite work though - it looks that Http.sys authentication is done BEFORE my policy selector is even invoked (see comment in the code snippet).
In a desparate and clueless attempt, I've fiddled around with setting AllowAnonymous to true, but that just seems to lead to Windows auth never being used.
Is there any way to select the auth scheme properly?
var host = new WebHostBuilder()
.ConfigureServices(services =>
{
services
.AddAuthentication("DynamicAuthenticationScheme")
.AddScheme<AuthenticationSchemeOptions, CustomAuth>("Custom", _ => { })
.AddPolicyScheme("DynamicAuthenticationScheme", "Default system policy", cfgOptions =>
{
cfgOptions.ForwardDefaultSelector = ctx =>
{
// Auth looks to be done beforehand already; if setting a breakpoint here, ctx.User is already given
if (ctx.Request.Headers.ContainsKey("CUSTOM-AUTH"))
return "Custom";
return HttpSysDefaults.AuthenticationScheme;
};
});
})
.UseHttpSys(options =>
{
options.Authentication.Schemes = AuthenticationSchemes.NTLM | AuthenticationSchemes.Negotiate;
options.Authentication.AllowAnonymous = false;
})
.UseUrls("http://localhost:7200")
.Configure(app =>
{
app.UseAuthentication();
app.Map(new PathString("/test"), cfg =>
cfg.Use(async (context, next) =>
{
await context.Response.WriteAsync($"Hello {context.User.Identity?.Name}");
}));
})
.Build();
await host.StartAsync();
I've "solved" this now by adding a custom middleware:
class MyMiddleware : IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
if(ShouldUseCustomAuth(context))
{
var authResult = await context.AuthenticateAsync("Custom");
if(authResult.Succeeded)
{
context.User = auth.Principal;
await next(context);
return;
}
}
await context.ChallengeAsync("Windows");
}
}
I'll leave the question open for a moment though in case anyone has a nicer solution (maybe involving policies after all?)
First of the all - I check in google and stack-overflow 2 days....
I found thousands examples and tutorials by still have missing point and don`t have full picture in the head.
So:
My architecture:
1) Identity server
2) 5 +/- MVC websites (Like Production website, Global admin, Help desk, etc...)(which have be protected by identity server )
3) Dozens micro services (which have be protected by identity server )
Now - What I not completely understand:
1) Login:
For now I setup the redirect flow. I Mean.... in website I setup Identity server like:
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "https://localhost:44396";
options.RequireHttpsMetadata = true;
options.ClientId = "<<Here is client ID>>";
options.ClientSecret = "<<HERE IS PASSWORD>>";
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("api1.read");
options.Scope.Add("offline_access");
});
And
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
Now, if user try to open page with Autorize attribute - user redirect to identity server login there and back to protected page. Everything working well.
But....
1) I want login on the MVC page. Without redirect to Identity Server.
I checked internet and found that I need use identityserver resource owner password flow
Then I setup IdentityServer as:
new Client {
ClientId = "myclient",
ClientName = "My first client",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,// GrantTypes.HybridAndClientCredentials,
ClientSecrets = new List<Secret> { new Secret("superSecretPassword".Sha256())},
AllowedScopes = new List<string> { "openid", "profile", "api1.read", IdentityServerConstants.StandardScopes.Email},
AllowOfflineAccess = true,
RedirectUris = { "https://localhost:44321/signin-oidc" },
RequireConsent = false
},
And in My MVC I can get token :
public static async Task HandleToken(this HttpClient client, string authority, string clientId, string secret, string apiName)
{
var accessToken = await client.GetRefreshTokenAsync(authority, clientId, secret, apiName);
client.SetBearerToken(accessToken);
}
private static async Task<string> GetRefreshTokenAsync(this HttpClient client, string authority, string clientId, string secret, string apiName)
{
var disco = await client.GetDiscoveryDocumentAsync(authority);
if (disco.IsError) throw new Exception(disco.Error);
var tokenResponse = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
{
UserName = "<<HERE IS USERNAME>>",
Password = "<<HERE IS PASSWORD>>",
Address = disco.TokenEndpoint,
ClientId = clientId,
ClientSecret = secret,
Scope = apiName
});
var user_info = await client.GetUserInfoAsync(new UserInfoRequest() { Address = disco.UserInfoEndpoint, Token = tokenResponse.AccessToken });
Here I have all user claims and Now I want set them in Controller => User
if (!tokenResponse.IsError) return tokenResponse.AccessToken;
return null;
}
Now I get token.....Good.........but
2 Questions:
1) How I can set the User Identity inside Controller.User (ClaimsPrincipal)?
**** UPDATE
I found the one solution
I can use HttpContext.SignInAsync and after I got token and user info from code above - I can do sign in for my Web MVC project and set manually user claims. If this is good approach?
2) All manipulation with User profile data, like ChangePassword, Update FirstName, LastName, etc...
How I need to do this??
Build Microservice for Identity Membership?
P.S - In IdentityServer I use Asp Identity :
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
})
.AddInMemoryIdentityResources(Config.Ids)
.AddInMemoryApiResources(Config.Apis)
.AddInMemoryClients(Config.Clients)
.AddAspNetIdentity<ApplicationUser>();
And last question is:
If I want to use DynamoDB as user store - then I need to build by custom Identity Provider?
(Correct??)
I found this solution in github, and I just need to update to Asp Core 3.1
https://github.com/c0achmcguirk/AspNetIdentity_DynamoDB
For the first question, you just need the configure your API for Identity Server then it will be populated automatically when the client made a proper request. (which includes its access token)
Sample API Configuration
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddCors(r => r.AddDefaultPolicy(o =>
o.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()));
services.AddAuthentication()
.AddJwtBearer(options =>
{
options.Audience = "apix"; // this apis scope
options.Authority = "http://localhost:5000"; // Identity server url
});
services.AddAuthorization(options =>
{
options.DefaultPolicy =
new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// ...
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
// ...
}
And you also need to decorate your API method with [Authorize] attribute.
For the second question, it is a matter of preference. There is a template named QuickStart that includes user operations with IdentityServer4 which handles that in an MVC way. You can also create WEB APIs and expose them. And you may not need to create a separate microservice for that since IdentityServer is a WEB application itself.
For the last question, people usually modify old repos to make it work with DynamoDb. Like this one
Edit:
For the question How to set up the MVC to set User Claims after ResourceOwner flow login
You need to implement a IProfileService service and register it in the startup. (IdentityServer)
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var subject = context.Subject;
var subjectId = subject.Claims.Where(x => x.Type == "sub").FirstOrDefault().Value;
var user = await _userManager.FindByIdAsync(subjectId);
var claims = GetClaimsFromUser(user,context.Caller); // here is the magic method arranges claims according to user and caller
context.IssuedClaims = claims.ToList();
}
We are setting up a new Asp.Net Core 3.0 Web Application. When authenticating with Auth0 against this WebApp, the App tries to Reach .eu.auth0.com with HTTP GET. To Reach this site behind our corporate Proxy, the app needs to send an User-Agent http header.
My attempt was to add a custom Http-Message-Handler, which adds this header to all Requests of the Authorization Backend.
public class CustomHttpMessageHandler : DelegatingHandler {
public CustomHttpMessageHandler() {
InnerHandler = new HttpClientHandler();
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
request.Headers.Add("User-Agent", "myApp/0.1.0");
return await base.SendAsync(request, cancellationToken);
}
}
This handler is set in the Startup.ConfigureServices():
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options => {
options.SaveToken = true;
options.Authority = domain;
options.Audience = Configuration["Auth0:ApiIdentifier"];
options.BackchannelHttpHandler = new CustomHttpMessageHandler();
});
When debugging, this Code is run and adds the User-Agent to the Request. The problem here is, that the app sends(before the GET) an HTTP OPTION Request to .eu.auth0.com which i am not able to modify.
Is there a way to set this Http-Header for authorization middlewares? Or even globally?
Or can i provide them with a whole custom Http-Client?
We have a HttpSys listener which should accept authentication as either NTLM, Negotiate or JWT.
Problem is that it looks like HttpSys rejects both preflight messages and messages with Bearer token (JWT)
Our listener is build like this
_host = new WebHostBuilder()
.UseHttpSys(options =>
{
options.Authentication.Schemes = AuthenticationSchemes.NTLM | AuthenticationSchemes.Negotiate;
options.Authentication.AllowAnonymous = false;
})
.UseUrls($"http://+:{PortNo}/")
.UseUnityServiceProvider(IocContainer)
.ConfigureServices(services => { services.AddSingleton(_startUpConfig); })
.UseStartup<StartUp>()
.Build();
We add CORS and Authentication to services:
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(o => o.AddPolicy("AllowAll", builder =>
{
builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader().AllowCredentials().WithOrigins("*");
}));
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = HttpSysDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
o.Events = new JwtBearerEvents { OnTokenValidated = context => AuthMiddleWare.VerifyJwt(context, _jwtPublicKey) };
});
We run an angular application in Chrome, which is rejected with the following error message
"Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. "
Also any Bearer token message is rejected. Debugging reveals that our code to verify JWT bearer is never reached (AuthMiddleWare.VerifyJwt)
My guess is that HttpSys rejects any message not carrying Either Ntlm or Negotiate token. Only I have no idea how to fix that
In .net Framework we used the AuthenticationSchemeSelectorDelegate to run the following code, which allowed OPTIONS messages and messages with Bearer token to pass through the HttpSys listener
public AuthenticationSchemes EvaluateAuthentication(HttpListenerRequest request)
{
if (request.HttpMethod == "OPTIONS")
{
return AuthenticationSchemes.Anonymous;
}
if (request.Headers["Authorization"] != null && request.Headers["Authorization"].Contains("Bearer "))
{
return AuthenticationSchemes.Anonymous;
}
return AuthenticationSchemes.IntegratedWindowsAuthentication;
}
We have this working now. Basically the problem was that allowing all three authentication methods is not a supported scenario in Asp Net Core.
So the trick was to implement our own authentication in the pipeline.
Also see this github issue:
https://github.com/aspnet/AspNetCore/issues/13135
A bit of background, I have an IdenityServer 4 project that I use to protect access to an mvc project that I have (Using ASP.NET Identity).
Now what I also wanted was an api that is protected via client credentials that returns some information.
What I did was make a new core api project and this was working fine with the client protection, however, I wanted to move the api so it was within IdenityServer.
e.g. localhost:5000/api/info/getinfo
Now I have moved the code over I get a 500 error when I use the attribute [Authorize(AuthenticationSchemes = "Bearer")]
I can use the DiscoveryClient to get a successful token using the credentials but can't with any request unless they are not authorized.
So in ID I set up my start up like this:
services.AddMvc();
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();
// Configure identity server with in-memory stores, keys, clients and scopes
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryPersistedGrants()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryClients(Config.GetClients(Configuration))
.AddInMemoryApiResources(Config.GetApiResources())
.AddAspNetIdentity<ApplicationUser>();
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = Configuration.GetSection("Authority").Value;
options.RequireHttpsMetadata = false;
options.ApiName = "IdentityInfoApi";
});
And then for my api call that is protected I tag it with: [Authorize(AuthenticationSchemes = "Bearer")]
but this returns me a 500 error, now when I use the tag: [Authorize] it works but that's because the user is logged into the mvc app and the response is an html page and not the json object i want.
At the moment I'm using a unit test to hit the api and the code looks like this:
var client = new HttpClient();
var disco = DiscoveryClient.GetAsync("https://localhost:5000").Result;
var tokenClient = new TokenClient(disco.TokenEndpoint, "client", "secret");
var tokenResponse = tokenClient.RequestClientCredentialsAsync("IdentityInfoApi").Result;
client.SetBearerToken(tokenResponse.AccessToken);
var response = client.GetAsync("https://localhost:5000/api/info/getinfo").Result;
if (!response.IsSuccessStatusCode)
{
var userResult = response.Content.ReadAsStringAsync().Result;
var result = JsonConvert.DeserializeObject<PagedUserList>(userResult);
Assert.NotNull(result);
}
Is there something wrong with my setup of ID, the client code or can you not use ID in this way?
Thank's for your help
After a lot of playing around I believe I found the fix.
You must define AddAuthentication() before AddIdentity() or in other words you must configure the api before Identity Server
It's fine to do it any way round if your api is external but not if it is within the Identity Server app it'self.
My new code looks like this:
//Configure api
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "https://localhost:5000";
options.RequireHttpsMetadata = false;
options.ApiName = "IdentityInfoApi";
});
//end
services.AddIdentity<ApplicationUser, IdentityRole>(config =>
{
config.SignIn.RequireConfirmedEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
services.Configure<AuthMessageSenderOptions>(Configuration.GetSection("SMTP"));
services.AddMvc();
// Configure identity server with in-memory stores, keys, clients and scopes
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryPersistedGrants()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryClients(Config.GetClients(Configuration))
.AddInMemoryApiResources(Config.GetApiResources())
.AddAspNetIdentity<ApplicationUser>();
Hope this helps anyone else