Accessing service in Startup class in ASP.NET Core - c#

I would like to update the DB after a user logged in to my app (using fb) and I am not sure how to use the DbContext within startup.cs.
startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<mysiteContext>(options =>
options.UseSqlServer(_configurationRoot.GetConnectionString("DefaultConnection")));
services.AddAuthentication(options =>
{
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddFacebook(options =>
{
options.AppId = "********";
options.AppSecret = "*********";
options.Events.OnCreatingTicket = context =>
{
var userFbId = context.User.Value<string>("id");
string userProfileImageUrl = $"https://graph.facebook.com/{userFbId}/picture?type=large";
//TODO: Save to DB infromation about the user and update last login date.
//This is where I am having the issue.
UserRepository userRepo = new UserRepository();
//Example how to add information to the claim.
var surname = context.User.Value<string>("last_name");
context.Identity.AddClaim(new Claim(ClaimTypes.Surname, surname));
return Task.FromResult(0);
};
})
.AddCookie();
And my UserRepository.cs:
public class UserRepository
{
private readonly mysiteContext _myDbContext;
private readonly short _languageTypeId;
public UserRepository(mysiteContext ctx)
{
_myDbContext = ctx;
_languageTypeId = Language.GetLanguageTypeId();
}
}
How can I pass mysiteContext to the UserRepository class?

You can do as follows:
services.AddScoped<UserRepository>(); // <-- Register UserRepository here
services.AddAuthentication(options =>
{
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddFacebook(options =>
{
options.AppId = "********";
options.AppSecret = "*********";
options.Events.OnCreatingTicket = context =>
{
........
ServiceProvider serviceProvider = services.BuildServiceProvider();
var userRepository = serviceProvider.GetService<UserRepository>();
// Do whatever you want to do with userRepository here.
.........
return Task.FromResult(0);
};
})
Alternatively you can also get UserRepository instance from context as follows:
var userRepository = context.HttpContext.RequestServices.GetService<UserRepository>();

I had to use my Dapper service inside 'ConfigureServices' section, where 'context' is not available. The easiest way for me was to get the service via 'BuildServiceProvider':
IDapper _dapper = services.BuildServiceProvider().GetService<IDapper>();

Related

User.Identity.IsAuthenticated is false in a non-auth methods after successful login in asp.net core

I am new in asp.net core. I try to login with discord as 3rd party login service (like sign-in with facebook, google).
I can login successfully and have my user object, claims and I can enter a class which has an authorize attribute. Below you can see that UserIdentity is fine.
But let assume that user wants to go back to the login page. In this case, I have to redirect him to the index but I want to check whether the user is authenticated or not by using Identity and unfortunately, it is false and no claims etc. As I understand, it may be related with cookies or something similar. I also use different attribute for class (not authorize but AllowAnonymous) You can see below my Identity object
I am sharing my authentication code
services.AddAuthentication(options =>
{
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
options.Cookie.MaxAge = options.ExpireTimeSpan;
options.SlidingExpiration = true;
options.EventsType = typeof(CustomCookieAuthenticationEvents);
options.AccessDeniedPath = "/auth/DiscordAuthFailed";
})
.AddJwtBearer(options =>
{
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration.GetValue<string>("Jwt:Issuer"),
ValidAudience = Configuration.GetValue<string>("Jwt:Audience"),
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(Configuration.GetValue<string>("Jwt:EncryptionKey")))
};
})
.AddOAuth("Discord",
options =>
{
options.AuthorizationEndpoint = "https://discord.com/api/oauth2/authorize";
options.TokenEndpoint = "https://discord.com/api/oauth2/token";
options.Scope.Add("identify");
options.Scope.Add("email");
options.Scope.Add("guilds.join");
options.Scope.Add("guilds.members.read");
options.CallbackPath = "/auth/oauthCallback";
options.ClientId = Configuration.GetValue<string>("Discord:ClientId");
options.ClientSecret = Configuration.GetValue<string>("Discord:ClientSecret");
options.UserInformationEndpoint = "https://discord.com/api/users/#me";
options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id");
options.ClaimActions.MapJsonKey(ClaimTypes.Name, "username");
options.ClaimActions.MapJsonKey(ClaimTypes.Email, "email");
options.ClaimActions.MapJsonKey(ClaimTypes.IsPersistent, "verified");
options.AccessDeniedPath = "/auth/DiscordAuthFailed";
options.Events = new OAuthEvents()
{
OnCreatingTicket = async context =>
{
var request = new HttpRequestMessage(HttpMethod.Get,
context.Options.UserInformationEndpoint);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
var response = await context.Backchannel.SendAsync(request,
HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
var user=(await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync())).RootElement;
context.RunClaimActions(user);
}
};
});
services.AddTransient();
So my question is that, what is the best approach to access userIdentify object in any class/method after successfully login?
After very long analyses, I found the problem.
I just changed the DefaultAuthenticateScheme as CookieAuthenticationDefaults
services.AddAuthentication(options =>
{
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
and called sign method in my login method.
var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
 
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, result.Principal);
after successfully sign-in, I cna get HttpContext.User object in any action.
You can use the GetUserAsync method that will check if a user has been logged in. You need to use the UserManager class that falls under the AspNetCore.Identity to a use the above method. In your case, it will look something like this:
You will first need to configure your UserManager class in Startup.cs by simply adding a parameter to the Configure method:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager)
{
// your code
}
And then you can use it in your Controller method:
[Route("Account")]
public class AccountController: Controller
{
private UserManager<ApplicationUser> _userManager;
public AccountController(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
[Route("Login")]
[AllowAnonymous]
public IActionResult Login()
{
ClaimsPrincipal currentUser = User;
var user =_userManager.GetUserAsync(User).Result;
if(user.Identity.IsAuthenticated)
{
//redirect here
}
return View();
}
}
You need to update your ConfigureServices to include the Default Identity:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseMySql(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
}

Pull configuration settings from an API in Startup.cs

In my Startup.cs class I have the following
services.AddAuthentication(options =>
{
options.DefaultScheme = "cookie";
options.DefaultChallengeScheme = "oidc";
options.DefaultSignOutScheme = "oidc";
})
.AddCookie("cookie", options =>
{
options.Cookie.Name = "cookie name";
options.Cookie.SameSite = SameSiteMode.Strict;
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "the authority URL";
options.ClientId = "the client identifier";
options.ClientSecret = "super secret";
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("api");
});
And contrary to having hardcoded values, I would like to call an API to get them. The API will return a model named FooAuthenticationConfiguration.
I don't know if I undestood correctly what you want to do, but tell me if this works for you:
In Startup.cs, create a method like:
private async Task GetConfiguration()
{
HttpClient client = new HttpClient();
var Response = await client.GetAsync("https://reqres.in/api/users/2");
}
Convert the ConfigureServices into an Async Method and calls in the beggining the GetConfiguration() like this:
public async void ConfigureServices(IServiceCollection services)
{
await GetConfiguration();
//....
}
You could do something like this.
This is the Program.cs from a Blazor WASM but should work similarly
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.Services.AddApiAuthorization(c =>
{
c.ProviderOptions.ConfigurationEndpoint = $"{builder.Configuration["ApiBaseUri"]}/_configuration/auth";
});
And then the Controller in your Api would look like this..
[ApiController]
[Route("_configuration")]
public class ConfigurationController
: ControllerBase
{
readonly IConfiguration configuration;
public ConfigurationController(
IConfiguration configuration)
{
this.configuration = configuration;
}
[HttpGet, Route("auth")]
public IActionResult GetAuthConfiguration()
{
var settings = new Dictionary<string, object?>()
{
{ "authority", configuration.GetValue<string>("oAuth:Authority") },
{ "scope", "openid profile" },
{ "response_type", "code" },
{ "redirect_uri", "myUrl" }
};
return new JsonResult(settings);
}
}

SignalR authentication issue

Im working on an ASP.NET core app and I need to implements a chat with SignalR.
Im using Identity with jwt to manage login/permissions and this works fine.
I followed this docs for implementing authentication in SignalR hub but it doesn't work, my hub functions can be reached even with the [Authorize] attribute on the hub.
This is my code for configuring the service
services
.AddAuthorization()
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = jwtSettings.Issuer,
ValidAudience = jwtSettings.Issuer,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.Secret)),
ClockSkew = TimeSpan.Zero
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) && (path.StartsWithSegments("/chat")))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
And this is the code I have for my hub
[Authorize]
public class Chat : Hub<IChatClientMethods>
{
private readonly ProjectHubContext _context;
private readonly UserManager<User> _userManager;
public Chat(ProjectHubContext context, UserManager<User> userManager) : base()
{
_context = context;
_userManager = userManager;
}
// This method is executed even with the [Authorize] attribute
public override async Task OnConnectedAsync()
{
await base.OnConnectedAsync();
Console.WriteLine("Connected");
var user = await _userManager.GetUserAsync(Context.User); //Always null
}
}
And finnaly the hub mapping
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<Chat>("/chat");
});
Why can I call hub functions from client when I provide a random test token ?
I found out why my [Authorize] tag didn't work.
I used a wrong using.
I used using MiNET.Plugins.Attributes;
instead of using Microsoft.AspNetCore.Authorization;

ASP.NET Core OAuth with basic auth for TokenEndpoint

I'm trying to use ASP.Net Core 2.2 with OAuth authentication. To use OAuth I use the AddOAuth method in the public void ConfigureServices(IServiceCollection services)in Startup.cs:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "Provider";
})
.AddCookie()
.AddOAuth("Provider", options =>
{
options.ClientId = Configuration["Provider:ClientId"];
options.ClientSecret = Configuration["Provider:ClientSecret"];
options.CallbackPath = new PathString("/callback");
options.AuthorizationEndpoint = "https://api.provider.net/auth/code";
options.TokenEndpoint = "https://api.provider.net/auth/token";
});
The problem is, that when the middleware tries to get an authorization code by using the TokenEndpoint, I receive a HTTP 401 because the provider expects a basic authentication header at this endpoint.
My question is, how can I tell the middleware to add a basic auth header to the TokenEndpoint request?
#Kirk Larkin Thanks for posting the link, this helped me alot to came up with a solution!
I created a DelegateHandler which adds a basic authentication header if the request is send to the TokenEndpoint:
public class AuthorizingHandler : DelegatingHandler
{
private readonly OAuthOptions _options;
public AuthorizingHandler(HttpMessageHandler inner, OAuthOptions options)
: base(inner)
{
_options = options;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if(request.RequestUri == new Uri(_options.TokenEndpoint))
{
string credentials = Convert.ToBase64String(ASCIIEncoding.ASCII.GetBytes(_options.ClientId + ":" + _options.ClientSecret));
request.Headers.Add("Authorization", $"Basic {credentials}");
}
return base.SendAsync(request, cancellationToken);
}
}
This DelegateHandler is used in the ConfigureService method:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "Provider";
})
.AddCookie()
.AddOAuth("Provider", options =>
{
options.ClientId = Configuration["Provider:ClientId"];
options.ClientSecret = Configuration["Provider:ClientSecret"];
options.CallbackPath = new PathString("/callback");
options.AuthorizationEndpoint = "https://api.provider.net/auth/code";
options.TokenEndpoint = "https://api.provider.net/auth/token";
var innerHandler = new HttpClientHandler();
options.BackchannelHttpHandler = new AuthorizingHandler(innerHandler, options);
//...
});
// ...
}

Asp.core 2 to get claims of curent user

I may get claims using DI in asp.core1, as in the below
public class UserService : IUserService
{
private IHttpContextAccessor _httpContext;
public UserService(IHttpContextAccessor httpContext)
{
_httpContext = httpContext;
}
}
In case with In asp.core 2, _httpContext.User.Identity.IsAuthenticated has the value false, and doesn't contain claims.
My startup.cs looks like this:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
// ...
};
});
services.AddCors();
services.AddMvc();
services.AddTransient<IUserService, UserService>();
services.AddTransient<IHttpContextAccessor, HttpContextAccessor();
}

Categories