Cache user profile with external authentication .NET Core IdentityServer4 - c#

New to the whole Identity concept but I've had a couple of Google searches and haven't found a reply I felt fitting.
I'm using .NET Core 1.0.0 with EF Core and IdentityServer 4 (ID4).
The ID4 is on a separate server and the only information I get in the client is the claims. I'd like to have access to the full (extended) user profile, preferrably from User.Identity.
So how to I set up so that the User.Identity is populated with all the properties on the ApplicationUser model without sending a DB request every time? I'd like the information to be stored in cache on authentication until the session ends.
What I don't want to do is that in each controller set up a query to get the additional information. All controllers on the client will be inheriting from a base controller, meaning I could DI some service if that's necessary.
Thanks in advance.
Client
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
AuthenticationScheme = "oidc",
SignInScheme = "Cookies",
Authority = Configuration.GetSection("IdentityServer").GetValue<string>("Authority"),
RequireHttpsMetadata = false,
ClientId = "RateAdminApp"
});
ID4
app.UseIdentity();
app.UseIdentityServer();
services.AddDeveloperIdentityServer()
.AddOperationalStore(builder => builder.UseSqlServer("Server=localhost;Database=Identities;MultipleActiveResultSets=true;Integrated Security=true", options => options.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name)))
.AddConfigurationStore(builder => builder.UseSqlServer("Server=localhost;Database=Identities;MultipleActiveResultSets=true;Integrated Security=true", options => options.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name)))
.AddAspNetIdentity<ApplicationUser>();
ApplicationUser Model
public class ApplicationUser : IdentityUser
{
[Column(TypeName = "varchar(100)")]
public string FirstName { get; set; }
[Column(TypeName = "varchar(100)")]
public string LastName { get; set; }
[Column(TypeName = "nvarchar(max)")]
public string ProfilePictureBase64 { get; set; }
}

If you want to transform claims on the identity server, for your case(you use aspnet identity) overriding UserClaimsPrincipalFactory is a solution(see Store data in cookie with asp.net core identity).
public class AppClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
{
public AppClaimsPrincipalFactory(
UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager,
IOptions<IdentityOptions> optionsAccessor) : base(userManager, roleManager, optionsAccessor)
{
}
public async override Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
{
var principal = await base.CreateAsync(user);
((ClaimsIdentity)principal.Identity).AddClaims(new[] {
new Claim("FirstName", user.FirstName)
});
return principal;
}
}
// register it
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, AppClaimsPrincipalFactory>();
Also you can use events(on the client application) to add extra claims into cookie, it provides claims until the user log out.
There are two(maybe more than) options:
First using OnTicketReceived of openidconnect authentication:
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
AuthenticationScheme = "oidc",
SignInScheme = "Cookies",
Authority = Configuration.GetSection("IdentityServer").GetValue<string>("Authority"),
RequireHttpsMetadata = false,
ClientId = "RateAdminApp",
Events = new OpenIdConnectEvents
{
OnTicketReceived = e =>
{
// get claims from user profile
// add claims into e.Ticket.Principal
e.Ticket = new AuthenticationTicket(e.Ticket.Principal, e.Ticket.Properties, e.Ticket.AuthenticationScheme);
return Task.CompletedTask;
}
}
});
Or using OnSigningIn event of cookie authentication
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies",
Events = new CookieAuthenticationEvents()
{
OnSigningIn = async (context) =>
{
ClaimsIdentity identity = (ClaimsIdentity)context.Principal.Identity;
// get claims from user profile
// add these claims into identity
}
}
});
See similar question for solution on the client application: Transforming Open Id Connect claims in ASP.Net Core

Related

How to create a simple login in ASP.NET core without database and authorize controller access

I am using ASP.NET Core 3.1. How should create a simple ASP.NET Core based login without the use of databases. Lets say instead of using a database, I have the login UserName and Password in the appsettings.json. I could easily access and get the appsettings values. But how should I go about implementing the login functionality and how should I configure services in the startup.cs (in Configure and ConfigureServices).
In Configure() method I have added the app.UseAuthentication();
When I login and move to the Controller class which uses the annotation [Authorize] I get the following error
An unhandled exception occurred while processing the request.
InvalidOperationException: No authenticationScheme was specified, and
there was no DefaultChallengeScheme found. The default schemes can be
set using either AddAuthentication(string defaultScheme) or
AddAuthentication(Action configureOptions).
Microsoft.AspNetCore.Authentication.AuthenticationService.ChallengeAsync(HttpContext
context, string scheme, AuthenticationProperties properties)
Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext
context)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext
context)
Firstly it's not a good idea to store user's credentials into appsettings.json . If you want to implement that for testing purpose , you can use cookie authentication :
Use cookie authentication without ASP.NET Core Identity
A simple code sample below is for your reference :
In the Startup.ConfigureServices method, create the Authentication Middleware services with the AddAuthentication and AddCookie methods:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Account/Login";
});
And enable middleware in Configure :
app.UseAuthentication();
app.UseAuthorization();
You can apply the [Authorize] attribute on protected controllers/actions. When user is not Authenticated , by defalut user will be redirect to LoginPath for cookie authentication . /Account/Login action will show username/password textbox to collect user's credential .
After user inputs credential and click submit button , the post method will check credential and create cookie :
public class AccountController : Controller
{
private readonly IOptions<List<UserToLogin>> _users;
public AccountController (IOptions<List<UserToLogin>> users)
{
_users = users;
}
[HttpPost]
public async Task<IActionResult> Login(UserToLogin userToLogin)
{
var user = _users.Value.Find(c => c.UserName == userToLogin.UserName && c.Password == userToLogin.Password);
if (!(user is null))
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name,userToLogin.UserName),
new Claim("FullName", userToLogin.UserName),
new Claim(ClaimTypes.Role, "Administrator"),
};
var claimsIdentity = new ClaimsIdentity(
claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties
{
RedirectUri = "/Home/Index",
};
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties);
}
return Redirect("/Accout/Error");
}
}
UserToLogin.cs :
public class UserToLogin
{
public string UserName { get; set; }
public string Password { get; set; }
}
appsettings.json:
{
"Users": [
{
"UserName": "xxxxxxxx",
"Password": "xxxxxxx"
},
{
"UserName": "xxxxxxxx",
"Password": "xxxxxxxxxxxx"
}
],
}
And register in ConfigureServices :
services.Configure<List<UserToLogin>>(Configuration.GetSection("Users"));

Azure Active Directory | Roles based authorization

I have tried various authentication scenarios of Azure Active Directory across internet. All examples are focused only on Authorization by Authentication. I was looking for Authorizing the user based on Roles from my AAD App Registration.
Auth() Scenarios,
For example,
..\Controller\ArtistController.cs:
public class ArtistController : ApiController
{
[Authorize(Roles = "Admin, InternalAdmin")]
public void Post(ArtistModel model)
{
// Do admin stuff here...
}
}
..\App_Start\Startup.Auth.cs [Not working]:
public void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters
{
SaveSigninToken = true,
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"],
RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
}
});
}
..\App_Start\Startup.Auth.cs [Working]:
public void ConfigureAuth(IAppBuilder app)
{
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = ConfigHelper.ClientId,
Authority = ConfigHelper.Authority,
RedirectUri = "<<Home_Url>>",
PostLogoutRedirectUri = ConfigHelper.PostLogoutRedirectUri,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
NameClaimType = "upn",
RoleClaimType = "roles", // The claim in the Jwt token where App roles are provided.
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error/ShowError?signIn=true&errorMessage=" + context.Exception.Message);
return System.Threading.Tasks.Task.FromResult(0);
}
}
});
}
I understand that OWIN can wire any middleware to handle incoming http requests. Auth Middlewares like OpenId, WindowsBearerToken,...
Is UseOpenIdConnectAuthentication() the only correct middleware to authorize web resources by roles over UseWindowsAzureActiveDirectoryBearerAuthentication() based on this example?
Please suggest.
Yes, OpenID is the only middleware that will work for this. There is no alternative at this point to OpenID Connect.
I found the best way to set the roles is to add these roles in the manifest and then hard code the logic to give different permissions to different users.
This is the best sample that I have found for this so far. You just need to add the connection string to Azure SQL for it to work. https://github.com/Azure-Samples/active-directory-dotnet-webapp-roleclaims

How to send bearer token to views in ASP NET MVC 5?

I have a .NET MVC and WEB API project. I want to call the WEB API controllers from javascript but I didn't find a way to send the token to my views. I want to add the bearer token in Viewbag variable, using the below code:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
//GetBearerToken doesn't exists
ViewBag.BearerToken = Request.GetOwinContext().GetBearerToken();
}
In _Layout.cshtml, I added the folowing code to set Authorization header for all ajax requests:
<script type="text/javascript">
window.token = '#Viewbag.BearerToken';
// Add Authorization header on ajax requests
if(window.token) {
(function setAjaxRequestsAuthorizationHeader(token) {
$.ajaxPrefilter(function onAjaxPrefilter(options) {
if (!options.beforeSend) {
options.beforeSend = function onBeforeSend(xhr) {
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
}
}
});
}(window.token));
window.token = null;
}
</script>
Below is my Startup configuration method:
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
//app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext(AppDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, User, int>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager),
getUserIdCallback: id => id.GetUserId<int>())
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
// In production mode set AllowInsecureHttp = false
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
// Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
}
To get a token I also tried (in the MVC Controller methods):
var token = new ClaimsPrincipal(User.Identity).Claims.FirstOrDefault(x => x.Type == "access_token")?.Value;
But the value of variable token is always null.
I have also inspected all claims values but I didn't find any possible token.
How can I send the token to controller views?
Solved
I solved the problem by doing the following steps:
In Startup.Auth.cs I added the folowing static property:
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
I have created an extension method for ApplicationUser class:
public static async Task<ClaimsIdentity> GenerateUserIdentityAsync(this ApplicationUser user, UserManager<ApplicationUser, int> manager, string type)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
// Genereate Bearer token
if (manager is ApplicationUserManager && type == DefaultAuthenticationTypes.ApplicationCookie)
{
var accessTokenFormat = Startup.OAuthOptions.AccessTokenFormat;
if (accessTokenFormat != null)
{
IDictionary<string, string> data = new Dictionary<string, string>
{
["userName"] = user.UserName
};
AuthenticationProperties properties = new AuthenticationProperties(data);
AuthenticationTicket ticket = new AuthenticationTicket(userIdentity, properties);
var token = accessTokenFormat.Protect(ticket);
userIdentity.AddClaim(new Claim("Access_Token", token));
}
}
return userIdentity;
}
I modified the method OnActionExecuting to send token to the client:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (Request.IsAuthenticated)
{
var identity = (ClaimsIdentity)User.Identity;
ViewBag.BearerToken = identity.Claims.FirstOrDefault(x => x.Type == "Access_Token")?.Value;
}
}
In Startup.Auth.cs use the extension method created at step 2 when creating Identity:
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser, int>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager,
DefaultAuthenticationTypes.ApplicationCookie),
getUserIdCallback: id => id.GetUserId<int>())

Identity Server 4: adding claims to access token

I am using Identity Server 4 and Implicit Flow and want to add some claims to the access token, the new claims or attributes are "tenantId" and "langId".
I have added langId as one of my scopes as below and then requesting that through identity server, but i get the tenantId also. How can this happen?
This the list of scopes and client configuration:
public IEnumerable<Scope> GetScopes()
{
return new List<Scope>
{
// standard OpenID Connect scopes
StandardScopes.OpenId,
StandardScopes.ProfileAlwaysInclude,
StandardScopes.EmailAlwaysInclude,
new Scope
{
Name="langId",
Description = "Language",
Type= ScopeType.Resource,
Claims = new List<ScopeClaim>()
{
new ScopeClaim("langId", true)
}
},
new Scope
{
Name = "resourceAPIs",
Description = "Resource APIs",
Type= ScopeType.Resource
},
new Scope
{
Name = "security_api",
Description = "Security APIs",
Type= ScopeType.Resource
},
};
}
Client:
return new List<Client>
{
new Client
{
ClientName = "angular2client",
ClientId = "angular2client",
AccessTokenType = AccessTokenType.Jwt,
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = new List<string>(redirectUris.Split(',')),
PostLogoutRedirectUris = new List<string>(postLogoutRedirectUris.Split(',')),
AllowedCorsOrigins = new List<string>(allowedCorsOrigins.Split(',')),
AllowedScopes = new List<string>
{
"openid",
"resourceAPIs",
"security_api",
"role",
"langId"
}
}
};
I have added the claims in the ProfileService:
public class ProfileService : IdentityServer4.Services.IProfileService
{
private readonly SecurityCore.ServiceContracts.IUserService _userService;
public ProfileService(SecurityCore.ServiceContracts.IUserService userService)
{
_userService = userService;
}
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
//hardcoded them just for testing purposes
List<Claim> claims = new List<Claim>() { new Claim("langId", "en"), new Claim("tenantId", "123") };
context.IssuedClaims = claims;
return Task.FromResult(0);
}
This is what i am requesting to get the token, the problem is i am only requesting the langId but I am getting both the tenantId and langId in the access token
http://localhost:44312/account/login?returnUrl=%2Fconnect%2Fauthorize%2Flogin%3Fresponse_type%3Did_token%2520token%26client_id%3Dangular2client%26redirect_uri%3Dhttp%253A%252F%252Flocalhost:5002%26scope%3DresourceAPIs%2520notifications_api%2520security_api%2520langId%2520navigation_api%2520openid%26nonce%3DN0.73617935552798141482424408851%26state%3D14824244088510.41368537145696305%26
Decoded access token:
{
"nbf": 1483043742,
"exp": 1483047342,
"iss": "http://localhost:44312",
"aud": "http://localhost:44312/resources",
"client_id": "angular2client",
"sub": "1",
"auth_time": 1483043588,
"idp": "local",
"langId": "en",
"tenantId": "123",
"scope": [
"resourceAPIs",
"security_api",
"langId",
"openid"
],
"amr": [
"pwd"
]
}
This answer was written for Identityserver4 on .Net core 2 to use it for .Net core 3, this answer may help you, but you need to test and change a few things.
I am using asp.net Identity and Entity Framework with Identityserver4.
This is my sample code, works well and JWT contains all roles and claims
You can see how to implement Identityserver4 with ASP.Net core identity here
http://docs.identityserver.io/en/release/quickstarts/6_aspnet_identity.html
https://github.com/IdentityServer/IdentityServer4.Samples/tree/dev/Quickstarts/6_AspNetIdentity
1- identity server startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
//Add IdentityServer services
//var certificate = new System.Security.Cryptography.X509Certificates.X509Certificate2(System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "LocalhostCert.pfx"), "123456");
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryIdentityResources(Configs.IdentityServerConfig.GetIdentityResources())
.AddInMemoryApiResources(Configs.IdentityServerConfig.GetApiResources())
.AddInMemoryClients(Configs.IdentityServerConfig.GetClients())
.AddAspNetIdentity<ApplicationUser>()
.AddProfileService<Configs.IdentityProfileService>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
//app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseIdentity();
// Adds IdentityServer
app.UseIdentityServer();
// Add external authentication middleware below. To configure them please see https://go.microsoft.com/fwlink/?LinkID=532715
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Account}/{action=Login}/{id?}");
});
}
2- IdentityServerConfig.cs
using IdentityServer4;
using IdentityServer4.Models;
using System.Collections.Generic;
namespace IdentityAuthority.Configs
{
public class IdentityServerConfig
{
// scopes define the resources in your system
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile()
};
}
// scopes define the API resources
public static IEnumerable<ApiResource> GetApiResources()
{
//Create api resource list
List<ApiResource> apiResources = new List<ApiResource>();
//Add Application Api API resource
ApiResource applicationApi = new ApiResource("ApplicationApi", "Application Api");
applicationApi.Description = "Application Api resource.";
apiResources.Add(applicationApi);
//Add Application Api API resource
ApiResource definitionApi = new ApiResource("DefinitionApi", "Definition Api");
definitionApi.Description = "Definition Api.";
apiResources.Add(definitionApi);
//Add FF API resource
ApiResource ffApi = new ApiResource("FFAPI", "Fule .netfx API");
ffApi.Description = "Test using .net 4.5 API application with IdentityServer3.AccessTokenValidation";
apiResources.Add(ffApi);
return apiResources;
}
// client want to access resources (aka scopes)
public static IEnumerable<Client> GetClients()
{
//Create clients list like webui, console applications and...
List<Client> clients = new List<Client>();
//Add WebUI client
Client webUi = new Client();
webUi.ClientId = "U2EQlBHfcbuxUo";
webUi.ClientSecrets.Add(new Secret("TbXuRy7SSF5wzH".Sha256()));
webUi.ClientName = "WebUI";
webUi.AllowedGrantTypes = GrantTypes.HybridAndClientCredentials;
webUi.RequireConsent = false;
webUi.AllowOfflineAccess = true;
webUi.AlwaysSendClientClaims = true;
webUi.AlwaysIncludeUserClaimsInIdToken = true;
webUi.AllowedScopes.Add(IdentityServerConstants.StandardScopes.OpenId);
webUi.AllowedScopes.Add(IdentityServerConstants.StandardScopes.Profile);
webUi.AllowedScopes.Add("ApplicationApi");
webUi.AllowedScopes.Add("DefinitionApi");
webUi.AllowedScopes.Add("FFAPI");
webUi.ClientUri = "http://localhost:5003";
webUi.RedirectUris.Add("http://localhost:5003/signin-oidc");
webUi.PostLogoutRedirectUris.Add("http://localhost:5003/signout-callback-oidc");
clients.Add(webUi);
//Add IIS test client
Client iisClient = new Client();
iisClient.ClientId = "b8zIsVfAl5hqZ3";
iisClient.ClientSecrets.Add(new Secret("J0MchGJC8RzY7J".Sha256()));
iisClient.ClientName = "IisClient";
iisClient.AllowedGrantTypes = GrantTypes.HybridAndClientCredentials;
iisClient.RequireConsent = false;
iisClient.AllowOfflineAccess = true;
iisClient.AlwaysSendClientClaims = true;
iisClient.AlwaysIncludeUserClaimsInIdToken = true;
iisClient.AllowedScopes.Add(IdentityServerConstants.StandardScopes.OpenId);
iisClient.AllowedScopes.Add(IdentityServerConstants.StandardScopes.Profile);
iisClient.AllowedScopes.Add("ApplicationApi");
iisClient.AllowedScopes.Add("DefinitionApi");
iisClient.AllowedScopes.Add("FFAPI");
iisClient.ClientUri = "http://localhost:8080";
iisClient.RedirectUris.Add("http://localhost:8080/signin-oidc");
iisClient.PostLogoutRedirectUris.Add("http://localhost:8080/signout-callback-oidc");
clients.Add(iisClient);
return clients;
}
}
}
3 - IdentityProfileService.cs
using IdentityServer4.Services;
using System;
using System.Threading.Tasks;
using IdentityServer4.Models;
using IdentityAuthority.Models;
using Microsoft.AspNetCore.Identity;
using IdentityServer4.Extensions;
using System.Linq;
namespace IdentityAuthority.Configs
{
public class IdentityProfileService : IProfileService
{
private readonly IUserClaimsPrincipalFactory<ApplicationUser> _claimsFactory;
private readonly UserManager<ApplicationUser> _userManager;
public IdentityProfileService(IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory, UserManager<ApplicationUser> userManager)
{
_claimsFactory = claimsFactory;
_userManager = userManager;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var sub = context.Subject.GetSubjectId();
var user = await _userManager.FindByIdAsync(sub);
if (user == null)
{
throw new ArgumentException("");
}
var principal = await _claimsFactory.CreateAsync(user);
var claims = principal.Claims.ToList();
//Add more claims like this
//claims.Add(new System.Security.Claims.Claim("MyProfileID", user.Id));
context.IssuedClaims = claims;
}
public async Task IsActiveAsync(IsActiveContext context)
{
var sub = context.Subject.GetSubjectId();
var user = await _userManager.FindByIdAsync(sub);
context.IsActive = user != null;
}
}
}
4 - In my client mvc core project I added 3 nuget packages
.Microsoft.AspNetCore.Authentication.Cookies
.Microsoft.AspNetCore.Authentication.OpenIdConnect
.IdentityModel
5- This is my startup.cs in my client mvc core project
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
//app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
//Setup OpenId and Identity server
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies",
AutomaticAuthenticate = true
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
Authority = "http://localhost:5000",
ClientId = "U2EQlBHfcbuxUo",
ClientSecret = "TbXuRy7SSF5wzH",
AuthenticationScheme = "oidc",
SignInScheme = "Cookies",
SaveTokens = true,
RequireHttpsMetadata = false,
GetClaimsFromUserInfoEndpoint = true,
ResponseType = "code id_token",
Scope = { "ApplicationApi", "DefinitionApi", "FFAPI", "openid", "profile", "offline_access" },
TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
}
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
6 - In my API I added this nuget package
.IdentityServer4.AccessTokenValidatio
and my startup.cs is like this
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
//IdentityServer4.AccessTokenValidation
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "http://localhost:5000",
RequireHttpsMetadata = false,
ApiName = "ApplicationApi"
});
app.UseMvc();
}
Now I can use [Authorize(Role="SuperAdmin, Admin")] in both client web app and API app.
User.IsInRole("Admin")
also I have access to claims
HttpContext.User.Claims
var q = (from p in HttpContext.User.Claims where p.Type == "role" select p.Value).ToList();
var q2 = (from p in HttpContext.User.Claims where p.Type == "sub" select p.Value).First();
I would like to provide my own answer after some rigorous research:
During the login process, the server will issue an authentication cookie with some of the claims of the user.
Then, the client will request an access token while providing the claims from the cookie, and the profile service will use the cookie claims to generate the access token claims.
Next, the client will request an id token, but this time it will use the claims from the access token.
Now the thing is, the default profile service of identity server populates the claims of the id token just by using the claims in the access token, while the default profile service of ASP.Net Identity, does look up all the user claims from the database store. This is a point of confusion.
For the identity server implementation, which claims end up in the access token? The claims associated with scopes which are API resources, as opposed to the claims in the id token, which are those associated with scopes which are identity resources.
Summary
Without ASP.NET Identity:
Login - identity server issues a cookie with some claims
Access token query - identity server adds claims from the cookie based on requested api scopes
Id token query - identity server adds claims from the access token based on requested identity scopes
With ASP.NET Identity:
Login - identity server issues a cookie with some claims
Access token query - identity server adds claims from the cookie based on requested api scopes
Id token query - identity server adds claims from the access token and the claims in the user store based on requested identity scopes
You should check context.RequestedClaimTypes and filter out claims, that were not requested.

c# web api owin authentication

I am very new to WebAPi Authentication even it seems OWIN being popular use. I dont understand why I should use EntityFramework for OWIN authentication as ApplicationDbContext is inhreting from IdentityDbContext and IdentityDbContext is in EntityFramework namespace. Below is procedure which is created automatically when we choose Individual User Accounts within WebApi project template:
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static string PublicClientId { get; private set; }
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
// In production mode set AllowInsecureHttp = false
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
// Uncomment the following lines to enable logging in with third party login providers
//app.UseMicrosoftAccountAuthentication(
// clientId: "",
// clientSecret: "");
//app.UseTwitterAuthentication(
// consumerKey: "",
// consumerSecret: "");
//app.UseFacebookAuthentication(
// appId: "",
// appSecret: "");
//app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
//{
// ClientId = "",
// ClientSecret = ""
//});
}
}
Within ConfigureAuth procedure ApplicationDbContext is referenced.
Could you pls help me to write simple Authentication with OWIN and not to use EntityFramework?
Thanks.
you donĀ“t need to use EF, yes, the template uses EF and ASPNET Identity to do the authentication, but you can start using the black template and add it without EF, look the following part of code:
Startup.cs
public class Startup
{
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuth(app);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider()
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
SimpleAuthorizationServerProvider.cs
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
if (context.UserName != "Admin")
{
context.SetError("upps!", "Wrong data");
return;
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("role", "user"));
context.Validated(identity);
}
}
Also, you can download a simple example here: http://1drv.ms/1mmaqtn
Regards,

Categories

Resources