How do i set up a default for ActiveAuthenticationSchemes? - c#

In my ASP.Net Core 2 project, I have a custom AuthenticationHandler middleware that i want to plug in.
public class BasicAuthenticationMiddleware : AuthenticationHandler<AuthenticationSchemeOptions>
{
public BasicAuthenticationMiddleware(IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var principal = new GenericPrincipal(new GenericIdentity("User"), null);
var ticket = new AuthenticationTicket(principal, new AuthenticationProperties(), "BasicAuth");
return Task.FromResult(AuthenticateResult.Success(ticket));
}
}
In my startup I have the following:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "BasicAuth";
options.DefaultChallengeScheme = "BasicAuth";
options.AddScheme("BasicAuth", x => {
x.DisplayName = "BasicAuthenticationMiddleware";
x.HandlerType = typeof(BasicAuthenticationMiddleware);
});
});
}
And finally my view controller:
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values/Works
[HttpGet]
[Route("Works")]
[Authorize(ActiveAuthenticationSchemes = "BasicAuth")]
public string Works()
{
return "works";
}
// GET api/values/DoesNotWork
[HttpGet]
[Route("DoesNotWork")]
[Authorize]
public string DoesNotWork()
{
return "does not work";
}
}
My authenticator HandleAuthenticateAsync will be called when I specify ActiveAuthenticationSchemes to my scheme name, but otherwise it will not. I have a demo app showing the behavior here: https://github.com/JohnPAguirre/AuthenticationSchemaProblem
I want my BasicAuthenticationMiddleware to log everyone in with my demo logic. How can i make the ActiveAuthenticationSchemes default to "BasicAuth" for all requests?
Anyone have any ideas on what I could be missing?

I did manage to set a default authentication scheme By setting the scheme I want as the only authentication scheme of the DefaultPolicy for authorization. Use the following below in your config. I have used it in between AddMvc and AddAuthentication and it works fine.
services.AddAuthorization(config => {
var def = config.DefaultPolicy;
config.DefaultPolicy = new AuthorizationPolicy(def.Requirements,
new List<string>(){ "BasicAuth" });
});

I don't think you can set a default, but you have some other options.
Create your own custom authorisation attribute:
public class BasicAuthAuthorizeAttribute : AuthorizeAttribute
{
public BasicAuthAuthorizeAttribute()
{
ActiveAuthenticationSchemes = "BasicAuth";
}
}
And use it on your actions like you would before:
[BasicAuthAuthorize]
public string SomeAction()
{
//snip
}
Add the Authorize attribute to all your actions and only override it where needed. To do that, in your `` method:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Filters.Add(new AuthorizeAttribute
{
ActiveAuthenticationSchemes = "BasicAuth"
});
});
//snip
}
And overriding it:
[AllowAnonymous]
public string UnsecureAction()
{
//snip
}

I have used a similar code and it works perfectly. Main difference I see is I used the AddScheme function chaining AddAuthentication instead of inside it's config.
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "BasicAuth";
options.DefaultChallengeScheme = "BasicAuth";
})
AddScheme<AuthenticationSchemeOptions, BasicAuthenticationMiddleware>
("BasicAuth", "BasicAuthenticationMiddleware", x => { });
Rest of the code seems fine.

Related

Adding a fake user when developing

I am trying to add a fake user when running the application in development mode. Insted of adding a fake JWT header is there a way to add some fake user details?
This is what I have done so far:
I created a fake filter:
internal class FakeUserFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
context.HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
{
new(ClaimTypes.NameIdentifier, "123"),
new(ClaimTypes.Name, "Test user"),
new(ClaimTypes.Email, "test#example.com"),
new(ClaimTypes.Role, "Admin")
}));
await next();
}
}
Added it to Controllers
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
//...
builder.Services.AddControllers(options =>
{
if (!builder.Configuration.GetValue<bool>("UseAuthorization"))
{
options.Filters.Add(new AllowAnonymousFilter());
options.Filters.Add(new FakeUserFilter());
}
});
//...
And later in the I enabled Authentication and Authorization
//...
app.UseAuthentication();
app.UseAuthorization();
//...
I still get an unauthorised error. As of now, I am just adding a global anonymous attribute by doing
if (app.Environment.IsDevelopment())
{
app.MapControllers().WithMetadata(new AllowAnonymousAttribute());
}
else
{
app.MapControllers();
}
This obviously doesn't give me a fake user. Is there a way to set the fake user in development mode? Any help is appreciated.
ActionFilter is not the right tool for this job. What you should do is to fake authentication handler. Just write some kind of TestAuthenticationHandler implementing AuthenticationHandler class.
Example:
public class TestAuthHandlerOptions : AuthenticationSchemeOptions
{
}
public class TestAuthHandler : AuthenticationHandler<TestAuthHandlerOptions>
{
public const string AuthenticationScheme = "Test";
public TestAuthHandler(IOptionsMonitor<TestAuthHandlerOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var claims = new List<Claim>
{
new(ClaimTypes.NameIdentifier, "123"),
new(ClaimTypes.Name, "Test user"),
new(ClaimTypes.Email, "test#example.com"),
new(ClaimTypes.Role, "Admin")
};
var identity = new ClaimsIdentity(claims, AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, AuthenticationScheme);
var result = AuthenticateResult.Success(ticket);
return Task.FromResult(result);
}
}
And register it:
builder.Services.AddAuthentication(TestAuthHandler.AuthenticationScheme)
.AddScheme<TestAuthHandlerOptions, TestAuthHandler>(TestAuthHandler.AuthenticationScheme, options => { });
And that's it. In such a way, you're going to have access to your fake user at HttpContext.

ASP.NET Core Web API custom AuthorizeAttribute issue

I am working on ASP.NET Core Web API. I am trying to create a custom Authorize attribute but I am stuck. I could not understand what I am missing. I have the following code for the Authorize attribute and filter:
public class AuthorizeAttribute : TypeFilterAttribute
{
public AuthorizeAttribute(params string[] claim) : base(typeof(AuthorizeFilter))
{
Arguments = new object[] { claim };
}
}
public class AuthorizeFilter : IAuthorizationFilter
{
readonly string[] _claim;
public AuthorizeFilter(params string[] claim)
{
_claim = claim;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var IsAuthenticated = context.HttpContext.User.Identity.IsAuthenticated;
var claimsIndentity = context.HttpContext.User.Identity as ClaimsIdentity;
if (IsAuthenticated)
{
bool flagClaim = false;
foreach (var item in _claim)
{
if (context.HttpContext.User.HasClaim("Role", item))
flagClaim = true;
}
if (!flagClaim)
{
//if (context.HttpContext.Request.IsAjaxRequest())
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; //Set HTTP 403
//else
// context.Result = new RedirectResult("~/Login/Index");
}
}
else
{
//if (context.HttpContext.Request.IsAjaxRequest())
//{
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; //Set HTTP 401 -
//}
//else
//{
// context.Result = new RedirectResult("~/Login/Index");
//}
}
return;
}
}
I have copied this code from somewhere and commented unnecessary lines.
Here is my controller class where I am trying to put this:
[Route("api/[controller]/[action]")]
[ApiController]
[Authorize]
public class JobController : ControllerBase
{
// GET: api/<JobController>
[HttpGet]
[ActionName("GetAll")]
public List<Job> Get()
{
return JobDataLog.GetAllJobQueue();
}
// GET api/<JobController>/5
[HttpGet("{ID}")]
[ActionName("GetByID")]
public Job Get(Guid ID)
{
return JobDataLog.GetJob(ID);
}
// GET api/<JobController>/5
[HttpGet]
[ActionName("GetCount")]
public int GetCount()
{
return JobDataLog.GetJobTotal();
}
}
Also the Configure and ConfigureService methods of Startup.cs
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(60);
});
var tokenKey = Configuration.GetValue<string>("TokenKey");
var key = Encoding.ASCII.GetBytes(tokenKey);
services.AddAuthentication(x => { x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; })
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
services.AddSingleton<IJWTAuthenticationManager>(new JWTAuthenticationManager(tokenKey));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseCookiePolicy();
app.UseSession();
app.Use(async (context, next) =>
{
var JWToken = context.Session.GetString("JWToken");
if (!string.IsNullOrEmpty(JWToken))
{
context.Request.Headers.Add("Authorization", "Bearer " + JWToken);
}
await next();
});
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
The problem is that even this controller has the Authorize attribute, all the actions are being called even the Authorize filter invalidates the authorization.
Also when I placed the following code in the OnAuthorization method:
context.Result = new StatusCodeResult(StatusCodes.Status401Unauthorized);
It blocked the access of all actions, including those which have an AllowAnnoynmous attribute.
Please help me, I have been stuck on this for last 3 hours.
If you really want to use a custom AuthorizeAttribute, here you go, this works. :)
You'll have a few squiggly lines, but VS will be able to automatically add the using statements.
The original code had multiple problems:
Setting Reponse.StatusCode doesn't actually lead to a response being returned.
HttpContext.User wouldn't be populated in the first place, because ASP.NET Core only attempts to authenticate the user and populate the user's claims/identity if an endpoint is secured with the built-in AuthorizeAttribute. The following code solves this by deriving from AuthorizeAttribute.
In this case the additional filter factory class wasn't needed, since you're not injecting dependencies. Though, if you had to inject, you'd be out of luck I think, because you couldn't derive both from TypeFilterAttribute and AuthorizeAttribute, and the claims list would be always empty.
Working code
public class MyAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
readonly string[] _requiredClaims;
public MyAuthorizeAttribute(params string[] claims)
{
_requiredClaims = claims;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var isAuthenticated = context.HttpContext.User.Identity.IsAuthenticated;
if (!isAuthenticated)
{
context.Result = new UnauthorizedResult();
return;
}
var hasAllRequredClaims = _requiredClaims.All(claim => context.HttpContext.User.HasClaim(x => x.Type == claim));
if (!hasAllRequredClaims)
{
context.Result = new ForbidResult();
return;
}
}
}
You should probably use policies instead
The reason why this works in such a crappy way is that the ASP.NET Core team doesn't want you to write custom Authorize Attributes. See this answer on the subject. The 'proper' way is to create policies, and assign your claim requirements to those policies. But I also think it's silly that authorization is so inflexible and lacking support for basic scenarios.

ASP.NET Core Web API, How can I access HttpContext in startup class

I'm trying to access HttpContext to get RemoteIpAddress and User-Agent, but within Startup.cs.
public class Startup
{
public Startup(IConfiguration configuration, IHttpContextAccessor httpContextAccessor)
{
Configuration = configuration;
_httpContextAccessor = httpContextAccessor;
}
public IConfiguration Configuration { get; }
public IHttpContextAccessor _httpContextAccessor { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
IdentityModelEventSource.ShowPII = true;
var key = Encoding.ASCII.GetBytes(Configuration.GetValue<string>("claveEncriptacion"));
var ip = _httpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString();
var userAgent = _httpContextAccessor.HttpContext.Request.Headers["User-Agent"].ToString();
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
//x.Audience = ip + "-" + userAgent;
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = true
};
});
With the previous code I have an error executing the project.
Unable to resolve service for type 'Microsoft.AspNetCore.Http.IHttpContextAccessor' while attempting to activate 'JobSiteMentorCore.Startup'.'
According to the ASP.NET Core documentation , only the following service types can be injected into the Startup constructor when using the Generic Host (IHostBuilder):
IWebHostEnvironment
IHostEnvironment
IConfiguration
So you cannot inject IHttpContextAccessor to Startup constructor.
However you can get DI resolved service in ConfigureServices method of the Startup class as follows:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IYourService, YourService>();
// Build an intermediate service provider
var serviceProvider = services.BuildServiceProvider();
// Resolve the services from the service provider
var yourService = serviceProvider.GetService<IYourService>();
}
But you can not get the HttpContext using IHttpContextAccessor similarly because HttpContext is null unless the code executed during any HttpRequest. So you have to do your desired operation from any custom middleware in Configure method of the Startup class as follows:
public class YourCustomMiddleMiddleware
{
private readonly RequestDelegate _requestDelegate;
public YourCustomMiddleMiddleware(RequestDelegate requestDelegate)
{
_requestDelegate = requestDelegate;
}
public async Task Invoke(HttpContext context)
{
// Your HttpContext related task is in here.
await _requestDelegate(context);
}
}
Then in the Configure method of the Startup class as follows:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware(typeof(YourCustomMiddleMiddleware));
}
I've finally found a possible solution, using middleware to validate the token.
I created a class ValidationHandler, and this class can use HttpContext.
public class ValidationRequirement : IAuthorizationRequirement
{
public string Issuer { get; }
public string Scope { get; }
public HasScopeRequirement(string scope, string issuer)
{
Scope = scope ?? throw new ArgumentNullException(nameof(scope));
Issuer = issuer ?? throw new ArgumentNullException(nameof(issuer));
}
}
public class ValidationHandler : AuthorizationHandler<ValidationRequirement>
{
IHttpContextAccessor _httpContextAccessor;
public HasScopeHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, HasScopeRequirement requirement)
{
var ip = _httpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString();
var userAgent = _httpContextAccessor.HttpContext.Request.Headers["User-Agent"].ToString();
//context.Succeed(requirement);
return Task.CompletedTask;
}
}
Finally in the Startup.cs class it is necessary to add the following.
services.AddAuthorization(options =>
{
options.AddPolicy("read:messages", policy => policy.Requirements.Add(new HasScopeRequirement("read:messages", "david")));
});
Thank you vidgarga. I would upvote but I have a new account. Your answer helped me on my project. I'm coming from f# land so I am including my implementation for any fsharpers that need this solution.
type HasScopeHandler() =
inherit AuthorizationHandler<HasScopeRequirement>()
override __.HandleRequirementAsync(context, requirement) =
let scopeClaimFromIssuer = Predicate<Claim>(fun (c: Claim) -> c.Type = "scope" && c.Issuer = requirement.Issuer)
let userDoesNotHaveScopeClaim = not (context.User.HasClaim(scopeClaimFromIssuer))
let isRequiredScope s = (s = requirement.Scope)
let claimOrNull = context.User.FindFirst(scopeClaimFromIssuer)
if (userDoesNotHaveScopeClaim) then
Task.CompletedTask
else
match claimOrNull with
| null -> Task.CompletedTask
| claim ->
let scopes = claim.Value.Split(' ')
let hasRequiredScope = scopes.Any(fun s -> isRequiredScope s)
if (hasRequiredScope) then
context.Succeed(requirement)
Task.CompletedTask
else
Task.CompletedTask

How to implement authorization using GraphQL.NET at Resolver function level?

I am looking for sample code and examples regarding how to implement authorization at resolver function level using GraphQL.NET and ASP.NET CORE 2.
Basically I am trying to prevent the execution of query if the request is not authorized.
Can anyone help me to get some good tutorials or code samples as reference for the implementation.
For graphql-dotnet/authorization, the page for AspNetCore has not been released, refer Add GraphQL.Server.Authorization.AspNetCore NuGet package #171.
You could implement Authorization.AspNetCore for your own use.
After implement Authorization.AspNetCore, you could configure the Authorize like:
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration, IHostingEnvironment hostingEnvironment)
{
Configuration = configuration;
Environment = hostingEnvironment;
}
public IConfiguration Configuration { get; }
public IHostingEnvironment Environment { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
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;
});
services.AddAuthentication(option =>
{
option.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
option.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
option.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);
services.AddGraphQL(options =>
{
options.EnableMetrics = true;
options.ExposeExceptions = Environment.IsDevelopment();
//options.
})
.AddGraphQLAuthorization(options =>
{
options.AddPolicy("Authorized", p => p.RequireAuthenticatedUser());
//var policy = new AuthorizationPolicyBuilder()
// .
//options.AddPolicy("Authorized", p => p.RequireClaim(ClaimTypes.Name, "Tom"));
});
//.AddUserContextBuilder(context => new GraphQLUserContext { User = context.User });
services.AddSingleton<MessageSchema>();
services.AddSingleton<MessageQuery>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseGraphQL<MessageSchema>("/graphql");
app.UseGraphQLPlayground(new GraphQLPlaygroundOptions()
{
Path = "/ui/playground"
});
app.UseGraphiQLServer(new GraphiQLOptions
{
GraphiQLPath = "/ui/graphiql",
GraphQLEndPoint = "/graphql"
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
Schema
public class MessageQuery : ObjectGraphType<Message>
{
public MessageQuery()
{
Field(o => o.Content).Resolve(o => "This is Content").AuthorizeWith("Authorized");
Field(o => o.SentAt);
Field(o => o.Sub).Resolve(o => "This is Sub");
}
}
For complete demo, refer GraphQLNet.
To get GraphQL.Net's authorization to work in ASP.NET Core, first install this package:
GraphQL.Server.Authorization.AspNetCore
In Startup.cs add the following in ConfigureServices. Make sure to add these using statements:
using GraphQL.Validation;
using GraphQL.Server.Authorization.AspNetCore;
public void ConfigureServices(IServiceCollection services)
{
//... other code
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services
.AddTransient<IValidationRule, AuthorizationValidationRule>()
.AddAuthorization(options =>
{
options.AddPolicy("LoggedIn", p => p.RequireAuthenticatedUser());
});
//... other code
}
Now you'll be able to use AuthorizeWith() at the resolver level to protect the field. Example:
public class MyQuery : ObjectGraphType
{
public MyQuery(ProductRepository productRepository)
{
Field<ListGraphType<ProductType>>(
"products",
resolve: context => productRepository.GetAllAsync()
).AuthorizeWith("LoggedIn");
}
}
You can also protect all queries by adding this.AuthorizeWith() to the top of the Query constructor like this:
public class MyQuery : ObjectGraphType
{
public MyQuery(ProductRepository productRepository)
{
this.AuthorizeWith("LoggedIn");
Field<ListGraphType<ProductType>>(
"products",
resolve: context => productRepository.GetAllAsync()
);
}
}
With that, any unauthenticated access to your GraphQL endpoint will be rejected.
Now in terms of logging someone in, there are many ways to do that. Here's a quick Cookie based authentication example:
Configure cookie based authentication in Startup.cs' ConfigureServices:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(o =>
{
o.Cookie.Name = "graph-auth";
});
Use mutation to log someone in:
public class Session
{
public bool IsLoggedIn { get; set; }
}
public class SessionType : ObjectGraphType<Session>
{
public SessionType()
{
Field(t => t.IsLoggedIn);
}
}
public class MyMutation : ObjectGraphType
{
public MyMutation(IHttpContextAccessor contextAccessor)
{
FieldAsync<SessionType>(
"sessions",
arguments: new QueryArguments(
new QueryArgument<NonNullGraphType<StringGraphType>> { Name = "password" }),
resolve: async context =>
{
string password = context.GetArgument<string>("password");
// NEVER DO THIS...for illustration purpose only! Use a proper credential management system instead. :-)
if (password != "123")
return new Session { IsLoggedIn = false };
var principal = new ClaimsPrincipal(new ClaimsIdentity("Cookie"));
await contextAccessor.HttpContext.SignInAsync(principal, new AuthenticationProperties
{
ExpiresUtc = DateTime.UtcNow.AddMonths(6),
IsPersistent = true
});
return new Session { IsLoggedIn = true };
});
}
}

Manually call the authorization flow from a middleware in mvc

I've setup oauth authorization in an MVC aspnet core 2 application.
It works as intended when I use the [Authorize] attribute but I can't get it to work with my middleware RequestHandler.
I tried creating a service that calls the context.ChallengeAsync() method but it fails when called from the middleware (the call never redirects).
If the user isn't already logged in the consent page is never shown and the token returned is null.
If the user was already logged in the call returns the token.
The service does work when called from inside a controller (instead of using [Authorize])
So how do I get it to work ? I want to make sure the user is authorized (so that i have access to a token) before continuing...
Here are the relevant code sections:
Startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OAuthOptionsDefaults.SchemeName;
})
.AddCookie()
.AddOAuth(OAuthOptionsDefaults.SchemeName,
options => { options.SaveTokens = true; /*other options*/ });
services.AddMvc();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IOAuthService, OAuthService>();
services.AddTransient<IRequestHandler, RequestHandler>();
}
public void Configure(IApplicationBuilder app, IRequestHandler requestHandler, ..)
{
app.UseExceptionHandler("/Home/Error");
app.UseStaticFiles();
app.UseAuthentication();
app.Map(new PathString("/myapi"), appbuilder =>
{
appbuilder.Run(async (context) =>
{
await requestHandler.Handle(context, "url to an api", "an api key");
});
});
app.UseMvcWithDefaultRoute();
}
}
RequestHandler.cs
public class RequestHandler : IRequestHandler
{
//[Authorize] doesn't work
public async Task Handle(HttpContext context, string apiUrl, string apiKey)
{
//injected IOAuthService _service;
//tried with and without context as parameter
var accessToken = await _service.GetAccesTokenAsync();
//do stuff ...
}
}
OAuthService.cs
public class OAuthService : IOAuthService
{
private async Task<string> GetAccesTokenAsyncHelper(HttpContext context)
{
var isAuthenticated = ((ClaimsIdentity)context.User.Identity)?.IsAuthenticated ?? false;
if (!isAuthenticated)
{
//tried with and without setting schemename
await context.ChallengeAsync(OAuthOptionsDefaults.SchemeName);
}
return await context.GetTokenAsync(OAuthOptionsDefaults.SchemeName, "access_token");
}
public async Task<string> GetAccesTokenAsync()
{
//injected IHttpContextAccessor _accessor;
var context = _accessor.HttpContext;
return await GetAccesTokenAsyncHelper(context);
}
public async Task<string> GetAccesTokenAsync(HttpContext context)
{
return await GetAccesTokenAsyncHelper(context);
}
}
Edit: made the question shorter and more to point in the hopes of someone answering it.
You need to add Identity to the user property of HttpContext
context.User.AddIdentity((ClaimsIdentity) principal.Identity);
I have added a tutorial here explaining how to authorize HttpContext in Middleware with JWT from the URL
hope this will be helpful :)

Categories

Resources