integrate blazor with aws cognito using openid - amazon-cognito

I am trying to integrate aws cognito in Blazor server app. I have configured user pool in cognito, and changed the startup as
services.AddAuthentication()
.AddCookie()
.AddOpenIdConnect(options =>
{
options.ResponseType = "code";
options.MetadataAddress = "address here";
options.ClientId = "clientid";
options.ClientSecret = "secret";
});
now how to display the cognito sign in page once the blazor application loads and then after sign in i want to redirect to blazor default page.
any help please.

I spent a couple of days trying to figure this all out. Following is what I came up with.
Program.cs
builder.Services.AddAuthentication(opt =>
{
opt.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
opt.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, opt =>
{
opt.Authority = builder.Configuration["AWS:Authority"]; //"https://cognito-idp.ap-southeast-2.amazonaws.com/ap-southeast-XXXXX/";
opt.ClientId = builder.Configuration["Secret_AWS:ClientId"];
opt.ClientSecret = builder.Configuration["Secret_AWS:ClientSecret"];
opt.ResponseType = OpenIdConnectResponseType.Code;
opt.SaveTokens = true;
opt.GetClaimsFromUserInfoEndpoint = true;
opt.UseTokenLifetime = true;
opt.Scope.Add("openid");
opt.Scope.Add("email");
opt.Scope.Add("profile");
opt.CallbackPath = "/signin-oidc"; //This is where you can change the callback path. Just remember to change the ReturnUrl in the Cognito console
opt.TokenValidationParameters = new TokenValidationParameters()
{
NameClaimType = "name"
};
opt.Events = new OpenIdConnectEvents //if you want to see what is going on during the authentication handshake, you can hook into these events as follows (only first one shown for brevity)
{
//order of calling during login. OnRedirectToIdentityProvider, OnMessageReceived, OnAuthorizationCodeReceived, OnTokenResponseReceived, OnTokenValidated, OnUserInformationReceived, OnTicketReceived,
OnRedirectToIdentityProvider = async ctx =>
{
AuthEventFlow = $"{AuthEventFlow}OnRedirectToIdentityProvider, ";
}
}
});
string? AuthEventFlow;
I found it useful studying the Cognito authentication endpoints Amazon Cognito Endpoints Link. It seems that a lot of the magic happens as a result of setting opt.GetClaimsFromUserInfoEndpoint = true. OpenIdConnect then gets all of the various Cognito endpoints by querying https://cognito-idp.Region.amazonaws.com/your user pool ID/.well-known/openid-configuration
In order to call the Cognito hosted UI, create the following Razor pages in your Pages folder
Login.cshtml
#page
#model AmmanControls.Pages.LoginModel
#{
}
Login.cshtml.cs
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace XXXX.Pages
{
public class LoginModel : PageModel
{
public async Task OnGet(string redirectUri)
{
await HttpContext.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties { RedirectUri = redirectUri });
//This was a critical point for me - it is the entry point to the OpenIdConnect login events
}
}
}
Logout.cshtml
#page
#model AmmanControls.Pages.LogoutModel
#{
}
Logout.cshtml.cs
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace XXX.Pages
{
public class LogoutModel : PageModel
{
public async Task<IActionResult> OnGetAsync()
{
await HttpContext.SignOutAsync();
return Redirect("/");
}
}
}
You then call these pages as follows:
Shared\LoginDisplay.razor
<AuthorizeView>
<Authorized>
<a>Hello, #context.User.Identity.Name </a>
<form method="get" action="logout">
<button type="submit" class="nav-link btn btn-link">Log out</button>
</form>
</Authorized>
<NotAuthorized>
Log in
</NotAuthorized>
</AuthorizeView>
Must admit I couldn't believe it when I finally got to click the Login button and all of the redirects just worked!
The problem I'm having now is that #context.User.Identity.Name is null even though the user is authenticated and I'm working out how to get the name of the authenticated user from the claims into the ClaimPrincipal. Will post an update when I figure that out.

Related

ASP.NET Core 6 session get returns null

I am using ASP.NET Core 6 with razor pages.
I can set the session like the code below but when I stop and run the project, the session get is returning null, but if I set the session again in a post request through a form and button it appears again!
I changed the options in program.cs but nothing seems to be working.
I am confused about this problem!
I have an example project in .NET 3 and it keeps the session but mine is not working
This is how I set the session:
HttpContext.Session.SetInt32(Constants.ShoppingCart, count);
And I get it like this in my Layout.cs:
#using Microsoft.AspNetCore.Http
#inject IHttpContextAccessor HttpContextAccessor
#if (HttpContextAccessor.HttpContext.Session.GetInt32(Constants.ShoppingCart) != null)
{
<a href="~/cart/Index" class="btn btn-primary navbar-btn">
#{
var count = HttpContextAccessor.HttpContext.Session.GetInt32(Constants.ShoppingCart);
}
<i class="fa fa-shopping-cart"></i>
<span>#count items in cart</span>
</a>
}
My program.cs file:
using AspDotNetApps.Core.Middleware;
using AutoMapper;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.FileProviders;
using System.Text.Json.Serialization;
var builder = WebApplication.CreateBuilder(args);
builder.WebHost
.CaptureStartupErrors(true)
.UseSetting("detailedErrors", "true");
builder.Services.AddControllersWithViews()
.AddJsonOptions(x => x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);
var keysDirectoryName = "mydir";
var keysDirectoryPath = Path.Combine(builder.Environment.ContentRootPath, keysDirectoryName);
if (!Directory.Exists(keysDirectoryPath))
{
Directory.CreateDirectory(keysDirectoryPath);
}
builder.Services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(keysDirectoryPath))
.SetApplicationName("store")
.SetDefaultKeyLifetime(TimeSpan.FromDays(30));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
builder.Services.AddAuthorization(options =>
{
options.AddPolicy(Constants.AdminRole, policy => policy.RequireRole(Constants.AdminRole));
});
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession(options =>
{
options.Cookie.Name = ".JewelryStore.Session";
options.IdleTimeout = TimeSpan.FromHours(24);
options.Cookie.IsEssential = true;
options.Cookie.HttpOnly = true;
});
builder.Services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
builder.Services.ConfigureApplicationCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(2);
options.SlidingExpiration = true;
options.LoginPath = $"/Admin/Login";
options.LogoutPath = $"/Admin/Logout";
options.AccessDeniedPath = $"/Areas/Admin/AccessDenied";
});
builder.Services.AddRazorPages();
builder.Services.AddCors(options => options.AddPolicy("Cors", builder =>
{
builder
.AllowAnyHeader()
.AllowAnyMethod()
.WithHeaders("accept", "content-type", "origin")
.AllowAnyOrigin();
}));
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddTransient<IEmailService, EmailService>();
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
Mapper.Initialize(x =>
{
x.AddProfile<MappingProfile>();
});
builder.Services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizePage("/Admin", "Main");
});
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
var context = services.GetRequiredService<ApplicationDbContext>();
context.Database.Migrate();
}
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error/500");
app.UseStatusCodePagesWithRedirects("/Error/{0}");
app.UseHsts();
}
app.UseCors("Cors");
app.UseSecurityHeadersMiddleware(
new SecurityHeadersBuilder()
.AddDefaultSecurePolicy());
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.MapRazorPages();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
// redirects the admin when using /admin route to the admin area
endpoints.MapAreaControllerRoute(
"admin",
"admin",
"Admin/{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute(
"default", "{controller=Home}/{action=Index}/{id?}");
});
app.Run();
I can set the session like the code below but when I stop and run the
project, the session get is returning null, but if I set the session
again in a post request through a form and button it appears again!I
am confused about this problem! I have an example project in .NET 3
and it keeps the session but mine is not working
Well, I think you are certainly misunderstanding between session and cookie concept. Session of course has nothing to deal with browsers and while application stops or shutdown session must not retain at all. So the project in .NET 3 you are referring definitely something dealing with cookies.
In addition, If you want to keep some value on browser and want to retrieve it even the browser closed and if cookies doesn't expire you would like it to get back in that scenario, you must need to deal with cookie for sure.
So far, I have gone through your requirement, you can only implement using cookie not session.
Razor Page cshtml.cs
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
private readonly IHttpContextAccessor _contextAccessor;
public IndexModel(ILogger<IndexModel> logger, IHttpContextAccessor contextAccessor)
{
_logger = logger;
_contextAccessor = contextAccessor;
}
public void OnGet()
{
var count = HttpContext.Request.Cookies["_count"];
if (count == null)
{
CookieOptions options = new CookieOptions();
options.Expires = DateTime.Now.AddHours(1);
_contextAccessor.HttpContext?.Response.Cookies.Append("_count", "600", options);
}
}
}
Razor Page cshtml
#page
#model IndexModel
#using Microsoft.AspNetCore.Http
#inject IHttpContextAccessor HttpContextAccessor
#{
ViewData["Title"] = "Home page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about building Web apps with ASP.NET Core.</p>
</div>
<h1>Current Count: #HttpContextAccessor.HttpContext.Request.Cookies["_count"]</h1>
program.cs
builder.Services.AddRazorPages();
builder.Services.AddHttpContextAccessor();
Output:
Note: You should see above infomration while you navigate to your browser application console.
Note: Your implementation and the behavior are correct in regards of session as it wouldn't keep anyting once the server or app restarted. However, if you would like to keep some value and want to retrieve it from browser side, you must need to use cookie. If you will need more information, please have a look on our official document here

Azure Active Directory Auth with ASP.NET Core 6 MVC

I created the whole process to use user authentication in my ASP.NET Core 6 MVC application, through Azure Active Directory. I'm using the Microsoft.Identity.Web API for this.
I can open the login screen, log in, but the callback (CallbackPath) is failing.
Here are parts of code and result with error after login:
Startup.cs
foreach (var conn in azureADTenants)
{
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(options =>
{
options.Instance = conn.ActiveDirectorySettings.Instance;
options.Domain = conn.ActiveDirectorySettings.Domain;
options.TenantId = conn.ActiveDirectorySettings.TenantId;
options.ClientId = conn.ActiveDirectorySettings.ClientId;
options.ClientSecret = conn.ActiveDirectorySettings.ClientSecret;
options.CallbackPath = conn.ActiveDirectorySettings.CallbackPath;
options.SignedOutCallbackPath = conn.ActiveDirectorySettings.SignedOutCallbackPath;
});
services.Configure<OpenIdConnectOptions>(conn.Name, options =>
{
var existingOnTokenValidatedHandler = options.Events.OnTokenValidated;
options.Events.OnTokenValidated = async context =>
{
await existingOnTokenValidatedHandler(context);
await context.HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
context.Principal);
};
});
}
appSettings.json
"ActiveDirectorySettings": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "aaahotmail.onmicrosoft.com",
"TenantId": "xxxxxxxx-xxx...",
"ClientId": "xxxxxxxx-xxx...",
"ClientSecret": "asasasasasas",
"CallbackPath": "/Login/signin-oidc",
"SignedOutCallbackPath": "/Login/signout-oidc"
}
In the Azure portal, in the redirect URIs section I entered:
https://localhost:81/Login/signin-oidc
In my controller class I added the redirect action
[Authorize]
[ActionName("signin-oidc")]
public IActionResult SignInRedirectFromActiveDirectory()
{
return null;
}
But I'm getting it in return:
So, what in practice do I need to do more to have login redirection in my controller (LoginController)?
Thank you very much in advance for your help!
I got something to no longer receive the error I mentioned.
The system still doesn't redirect to my controller, but passes authentication in AD and then returns to my login controller (where I originally called /Login/Index).
In startup.cs i added the following:
Ref: https://www.youtube.com/watch?v=S_xDAB_s-GM&list=WL&index=3
Thank you for your help

How Use Authentication and Authoriziation of my IdentityServer with itself?

I have an Api with Name MyApi and I use another asp.net core application with Identityserver4 for Protect MyApi,Now I don't have any problem in MyApi but,I want to save my Users's NationalCode ,So I should save this in my IdentityServer Database,But can't Get UserId (with User.Identity.Name) in my IdentityServer Project,I had same problem in my previose question
User.Identity.Name is null in my ASP.NET Core Web API
Now I have this problem in my IdentityServer4 project,So
Can I use Of MyApi token Or I should get a new token for send request to my idenittyserver4 project?
If I can MyAPI token ,How should I add configuration to solve the problem?
If I should take new token for my IdentityServer4 project,DO I need to want users to login again?!!!
Edit
I found a tutorail in below link but My Problem not solved Yet.
http://docs.identityserver.io/en/latest/topics/add_apis.html
I have seed my IdentityDatabase with below method
public async Task AddIdenityServerApiToResources(IApplicationBuilder app)
{
using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
serviceScope.ServiceProvider.GetRequiredService<PersistedGrantDbContext>().Database.Migrate();
var ccontext = serviceScope.ServiceProvider.GetRequiredService<ConfigurationDbContext>();
ccontext.Database.Migrate();
//=============================================================
ccontext.ApiResources.Add(new ApiResource(IdentityServerConstants.LocalApi.ScopeName) {
UserClaims =
{
JwtClaimTypes.Name,
JwtClaimTypes.Subject,
JwtClaimTypes.Role,
}
}.ToEntity());
//Add ApiResource To Client's Scope
var Clients = ccontext.Clients.Include(e => e.AllowedScopes);
foreach (var item in Clients)
{
item.AllowedScopes.Add(new IdentityServer4.EntityFramework.Entities.ClientScope() { Scope = IdentityServerConstants.LocalApi.ScopeName });
}
var Count = await ccontext.SaveChangesAsync();
if (Count > 0)
{
}
}
}
In IdentityServer4 startup.cs ConfigureServices
You should treat the api as if it's like any other api that needs to be secured by idserver4.
meaning: use AddAuthentication and AddJWTToken:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https:// idserver4 ";
options.RequireHttpsMetadata = true;
options.Audience = "api name";
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
};
});
in API controller :
use Authorize Attirbute and determine the authentication scheme like this:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
I solved the problem with below link:
http://docs.identityserver.io/en/latest/topics/add_apis.html
But the problem was where that I don't used Authorize on my controller with LocalApi.PolicyName policy
[Route("localApi")]
[Authorize(LocalApi.PolicyName)]
public class LocalApiController : ControllerBase
{
public IActionResult Get()
{
// omitted
}
}
after that the prolem was solvled

User.Identity.IsAuthenticated returns false after login while using OpenId Connect with Auth0

I am trying to implement user authentication in an ASP.Net Core (v2.1) MVC application using OpenId Connect and Auth0. I have the required configurations stored in the AppSettings files and application runs well till the Auth0 login page comes. Post login it hits the Callback URL which basically invokes a method (method name is Callback) in my Account Controller. In the callback method I am trying to get the access token if the user is authenticated. However, the User.Identity.IsAuthenticated returns false. Here is my code in the Startup.cs file--
public void ConfigureServices(IServiceCollection services)
{
//Set Cookie Policy
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;
});
// Add authentication services
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect("Auth0", options => {
options.Authority = $"https://{Configuration["Auth0:Domain"]}";
options.ClientId = Configuration["Auth0:ClientId"];
options.ClientSecret = Configuration["Auth0:ClientSecret"];
options.ResponseType = "code";
options.Scope.Clear();
options.Scope.Add("openid");
options.CallbackPath = new PathString("/oauth/callback");
options.ClaimsIssuer = "Auth0";
options.SaveTokens = true;
options.Events = new OpenIdConnectEvents
{
// handle the logout redirection
OnRedirectToIdentityProviderForSignOut = (context) =>
{
var logoutUri = $"https://{Configuration["Auth0:Domain"]}/v2/logout?client_id={Configuration["Auth0:ClientId"]}";
var postLogoutUri = context.Properties.RedirectUri;
if (!string.IsNullOrEmpty(postLogoutUri))
{
if (postLogoutUri.StartsWith("/"))
{
// transform to absolute
var request = context.Request;
postLogoutUri = request.Scheme + "://" + request.Host + request.PathBase + postLogoutUri;
}
logoutUri += $"&returnTo={ Uri.EscapeDataString(postLogoutUri)}";
}
context.Response.Redirect(logoutUri);
context.HandleResponse();
return Task.CompletedTask;
}
};
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
And here is my code in the Account Controller
public class AccountController : Controller
{
public async Task Login(string returnUrl = "/")
{
await HttpContext.ChallengeAsync("Auth0", new AuthenticationProperties() { RedirectUri = returnUrl });
}
[Authorize]
public async Task Logout()
{
await HttpContext.SignOutAsync("Auth0", new AuthenticationProperties
{
RedirectUri = Url.Action("Index", "Home")
});
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
public IActionResult AccessDenied()
{
return View();
}
[Authorize]
public IActionResult Claims()
{
return View();
}
[Route("/oauth/callback")]
public async Task<ActionResult> CallbackAsync()
{
if (User.Identity.IsAuthenticated)
{
string accessToken = await HttpContext.GetTokenAsync("access_token");
}
return RedirectToAction("Claims", "Account");
}
}
Please help. Any help will be appreciated.
Thanks,
Amit Anand
In fact i'm not sure why your custom CallbackAsync method fires during OIDC login . The callback url of OIDC middleware will handle token valiation ,token decode,exchange token and finally fill the user principle . You shouldn't handle the process and let OIDC middlware handle it , so change the route of the CallbackAsync method(or change the CallbackPath in OIDC middleware , but of course the url should match the url config in Auth0's portal ) , for example : [Route("/oauth/callbackAfterLogin")] .
After change that , the process will be : user will be redirect to Auth0 for login -->Auth0 validate the user's credential and redirect user back to url https://localhost:xxx/oauth/callback-->OIDC middlware handle token --> authentication success . If you want to redirect to CallbackAsync(route is /oauth/callbackAfterLogin) and get tokens there , you can directly pass the url in ChallengeAsync method when login :
await HttpContext.ChallengeAsync("Auth0",
new AuthenticationProperties() { RedirectUri = "/oauth/callbackAfterLogin"});

Secure API with JWT access token

I am playing around with the openiddict Authorization code flow sample and all is working well.
https://github.com/openiddict/openiddict-samples/tree/dev/samples/CodeFlow
However, I want to make certain changes and I am struggling to do this. I would like to configure to use JWT tokens instead of the default opaque tokens, and also separate into an authorization server and a resource server. I also have an MCV web app that will communicate with the resource server via a httpClient.
Auth Server.Startup
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
options.UseOpenIddict();
});
// Register the Identity services.
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserNameClaimType = OpenIdConnectConstants.Claims.Name;
options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject;
options.ClaimsIdentity.RoleClaimType = OpenIdConnectConstants.Claims.Role;
});
services.AddOpenIddict()
.AddCore(options =>
options.UseEntityFrameworkCore()
.UseDbContext<ApplicationDbContext>();
})
// Register the OpenIddict server handler.
.AddServer(options =>
{
options.UseMvc();
options.EnableAuthorizationEndpoint("/connect/authorize")
.EnableLogoutEndpoint("/connect/logout")
.EnableTokenEndpoint("/connect/token")
.EnableUserinfoEndpoint("/api/userinfo");
options.RegisterScopes(OpenIdConnectConstants.Scopes.Email,
OpenIdConnectConstants.Scopes.Profile,
OpenIddictConstants.Scopes.Roles);
options.AllowAuthorizationCodeFlow();
options.EnableRequestCaching();
options.DisableHttpsRequirement();
options.UseJsonWebTokens();
options.AddEphemeralSigningKey();
});
}
As this is no longer a resource server I have removed the validation parts as I don't think this is required. And as I want to use JWT I have un-commented the following lines:
options.UseJsonWebTokens();
options.AddEphemeralSigningKey();
The authorization endpoint returns a SignIn result exactly like the sample, which redirects to the MVC app which then issues an authentication cookie. I can now access protected resources on my MVC APP.
MVC APP startup
public void ConfigureServices(IServiceCollection services)
{
services.Configure<PortalDetails>(options => Configuration.GetSection("PortalDetails").Bind(options));
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(opts =>
{
opts.LoginPath = "/login";
opts.LogoutPath = "/logout";
})
.AddJwtBearer(options =>
{
//Authority must be a url. It does not have a default value.
options.Authority = "http://localhost:54540/";
options.Audience = "mvc"; //This must be included in ticket creation
options.RequireHttpsMetadata = false;
options.IncludeErrorDetails = true; //
options.TokenValidationParameters = new TokenValidationParameters()
{
NameClaimType = "sub",
RoleClaimType = "role"
};
})
.AddOpenIdConnect(options =>
{
// Note: these settings must match the application details
// inserted in the database at the server level.
options.ClientId = "mvc";
options.ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654";
options.RequireHttpsMetadata = false;
options.GetClaimsFromUserInfoEndpoint = false; // TODO: If this if true then it doesnt work??
options.SaveTokens = true;
// Use the authorization code flow.
options.ResponseType = OpenIdConnectResponseType.Code;
options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;
// Note: setting the Authority allows the OIDC client middleware to automatically
// retrieve the identity provider's configuration and spare you from setting
// the different endpoints URIs or the token validation parameters explicitly.
options.Authority = "http://localhost:54540/";
options.Scope.Add("email");
options.Scope.Add("roles");
options.SecurityTokenValidator = new JwtSecurityTokenHandler
{
// Disable the built-in JWT claims mapping feature.,
InboundClaimTypeMap = new Dictionary<string, string>()
};
options.TokenValidationParameters.NameClaimType = "name";
options.TokenValidationParameters.RoleClaimType = "role";
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddHttpClient<IApiGatewayClient, ApiGatewayClient>();
services.AddSingleton<ITokenProvider, TokenProvider>();
}
When calling the resource server I use:
string accessToken = await HttpContext.GetTokenAsync("access_token");
and I can see an access token, I attach that to my http request:
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
but the result is forbidden.
Finally, I have a protected resource server:
Resource.Startup
public void ConfigureServices(IServiceCollection services)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();
//Add authentication and set default authentication scheme
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) //same as "Bearer"
.AddJwtBearer(options =>
{
//Authority must be a url. It does not have a default value.
options.Authority = "http://localhost:54540";
options.Audience = "mvc"; //This must be included in ticket creation
options.RequireHttpsMetadata = false;
options.IncludeErrorDetails = true; //
options.TokenValidationParameters = new TokenValidationParameters()
{
NameClaimType = OpenIdConnectConstants.Claims.Subject,
RoleClaimType = OpenIdConnectConstants.Claims.Role,
};
});
services.AddMvc();
}
I would like to know if this is the correct setup for my scenario, as I am getting a forbidden result from my resource server.
Thanks
Here is a package which
Makes integrating JWT Bearer Token Security in your Asp Net Core 2.0+ app a breeze!
Azure Active Directory auth integration.
Facebook auth integration.
Twitter auth integration.
Google auth integration.
Also, Swagger UI integration!
It is called AspNetCore.Security.Jwt
GitHub:
https://github.com/VeritasSoftware/AspNetCore.Security.Jwt
The package integrates JWT bearer token into your app as below:
1. Implement IAuthentication interface in your app
using AspNetCore.Security.Jwt;
using System.Threading.Tasks;
namespace XXX.API
{
public class Authenticator : IAuthentication
{
public async Task<bool> IsValidUser(string id, string password)
{
//Put your id authenication here.
return true;
}
}
}
2. In your Startup.cs
using AspNetCore.Security.Jwt;
using Swashbuckle.AspNetCore.Swagger;
.
.
public void ConfigureServices(IServiceCollection services)
{
.
.
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "XXX API", Version = "v1" });
});
services.AddSecurity<Authenticator>(this.Configuration, true);
services.AddMvc().AddSecurity();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
.
.
.
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "XXX API V1");
});
app.UseSecurity(true);
app.UseMvc();
}
3. In your appsettings.json
Note:- You can put these settings in Secret Manager by using Manage User Secrets menu (right-click your Project).
{
"SecuritySettings": {
"Secret": "a secret that needs to be at least 16 characters long",
"Issuer": "your app",
"Audience": "the client of your app",
"IdType": "Name",
"TokenExpiryInHours" : 2
},
.
.
.
}
Then you will get endpoints automatically:
/token
/facebook
When you call these endpoints and are successfully authenticated, you will get back a JWT Bearer Token.
In your Controller that you want to secure
You must mark the Controller or Action that you want to secure with Authorize attribute like:
using Microsoft.AspNetCore.Mvc;
.
.
.
namespace XXX.API.Controllers
{
using Microsoft.AspNetCore.Authorization;
[Authorize]
[Route("api/[controller]")]
public class XXXController : Controller
{
.
.
.
}
}
In Swagger UI, you will automatically see these endpoints.