// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { // retrieve smtp config from environment variable (needed for configuring both identity email confirm and email service) string smtpConfig = Configuration.GetValue <string>("BOOKING_SMTP"); // determine whether email confirmation should be configured (enable for testing) bool emailConfirmation = string.IsNullOrEmpty(smtpConfig) ? HostingEnvironment.IsEnvironment("Development_WebAPI_Test") ? true : false : true; // configure data protection rules explicitly (linux hosts require this for the persistence of keys) // note: for production environments, set up a protector to interface with an external HSM/Key management system services.AddDataProtection() .SetApplicationName("angularbooking") .PersistKeysToFileSystem(new DirectoryInfo("/keys")); // configure CORS services.AddCors(options => { options.AddPolicy("Default", builder => builder. AllowAnyHeader(). AllowAnyOrigin(). AllowAnyMethod(). AllowCredentials() ); }); // configure CSRF services.AddAntiforgery(options => { options.HeaderName = "X-XSRF-TOKEN"; options.SuppressXFrameOptionsHeader = false; }); // configure MVC services.AddMvc(options => { options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()); }) .SetCompatibilityVersion(CompatibilityVersion.Version_2_1) .AddJsonOptions(options => { options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc; }); services.AddSpaStaticFiles(conf => { conf.RootPath = "ClientApp/dist"; }); // configure global CORS policy services.Configure <MvcOptions>(options => { options.Filters.Add(new CorsAuthorizationFilterFactory("Default")); }); // add EF services.AddEntityFrameworkSqlite() .AddDbContext <ApplicationDbContext>(options => { if (HostingEnvironment.IsEnvironment("Development_WebAPI_Test")) { // configure in-memory WebAPI test environment options.UseSqlite(_connection); } else { options.UseSqlite("DataSource=local.db"); } }); // add Identity services.AddIdentity <User, IdentityRole>(config => { config.SignIn.RequireConfirmedEmail = emailConfirmation; }) .AddEntityFrameworkStores <ApplicationDbContext>() .AddDefaultTokenProviders(); // add JWT auth // clear default claims JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); // specify jwt key string jwtKey = Configuration["JWT_KEY"]; // configure default authentication scheme services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(options => { options.SaveToken = true; options.TokenValidationParameters = new TokenValidationParameters { ValidIssuer = Configuration["JWT_ISSUER"], ValidAudience = Configuration["JWT_AUDIENCE"], // todo - use injected key from docker/kubernetes secret manager IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(jwtKey)), ClockSkew = TimeSpan.Zero, }; options.Events = new JwtBearerEvents { OnTokenValidated = (content) => { // get service for jwt manager IJwtManager jwtManager = content.HttpContext.RequestServices.GetService <IJwtManager>(); JwtSecurityToken token = (JwtSecurityToken)content.SecurityToken; // check against revocation list, and fail auth if on list if (jwtManager.IsRevoked(token)) { content.Fail("Refused: token has been revoked"); return(Task.CompletedTask); } return(Task.CompletedTask); } }; }); services.AddOData(); // add service for sending basic emails (BasicEmailService for local testing only, NOT for production use) string emailPickupDirectory = HostingEnvironment.IsEnvironment("Development_WebAPI_Test") ? Path.Combine(Environment.CurrentDirectory, "test_email") : null; services.AddTransient <IEmailService, BasicEmailService>(f => new BasicEmailService(emailPickupDirectory, smtpConfig)); // add service for accessing application data layer services.AddScoped <IUnitOfWork, DbUnitOfWork>(); // service for managing JWT creation and revocation services.AddSingleton <IJwtManager, InMemoryJwtManager>(f => new InMemoryJwtManager(Configuration, jwtKey)); }