IdentityServer 4 WsFederation - How to get access token for calling API - asp.net-core

I am using Identity Server 4 with the Ws-Federation plugin. Identity Server is configured to connect to Azure AD for authentication. Here is the relevant code from Identity Server project:
public void ConfigureServices(IServiceCollection services)
{
var rsaCertificate = new X509Certificate2("rsaCert.pfx", "1234");
services.AddRazorPages();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<User, IdentityRole>(options =>
{
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(2);
options.Lockout.MaxFailedAccessAttempts = 3;
})
.AddDefaultUI()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddUserStore<CustomUserStore>()
.AddUserManager<CustomUserManager>()
.AddDefaultTokenProviders();
services.AddTransient<IUserStore<User>, CustomUserStore>();
services.AddTransient<IEmailSender, EmailSender>();
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
// see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
options.EmitStaticAudienceClaim = true;
})
.AddSigningCredential(rsaCertificate)
.AddInMemoryIdentityResources(IdentityConfig.IdentityResources)
.AddInMemoryApiScopes(IdentityConfig.ApiScopes)
.AddInMemoryClients(IdentityConfig.Clients)
.AddAspNetIdentity<User>()
.AddWsFederationPlugin(options =>
{
options.Licensee = "Licensee";
options.LicenseKey = "LicenseKey";
})
.AddInMemoryRelyingParties(new List<RelyingParty>());
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
})
.AddWsFederation(options =>
{
options.Wtrealm = "Azure AD App Id";
options.MetadataAddress = "WSFed metadata URL from Azure AD App";
options.Events.OnSecurityTokenValidated = SecurityTokenValidated;
})
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(IdentityConfig.SessionTimeoutInMinutes);
options.SlidingExpiration = true;
options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.None;
});
}
I have an API protected with JWT bearer authentication which is connected to the same Identity Server. The relevant code from the API (Please note that https://localhost:5001 is the address which the Identity Server is running in):
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://localhost:5001";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
});
}
I have an MVC client as well which is connected to the same Identity Server. I was able to successfully authenticate users from the MVC client. Now, what I would like to do is to call a protected API endpoint in the API project from within the MVC client. I haven't found any way to get the access token necessary for calling the protected API. Relevant code from the MVC client:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.Cookie.Name = "aspnetcorewsfed";
options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.None;
options.SlidingExpiration = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(Configuration.GetValue<int?>("SessionTimeoutInMinutes") ?? 15);
})
.AddWsFederation(options =>
{
options.MetadataAddress = "https://localhost:5001/wsfed"; // Address of the Identity Server
options.RequireHttpsMetadata = false;
options.Wtrealm = "mvc"; // ClientId registered in Identity Server
options.CallbackPath = "/";
options.SkipUnrecognizedRequests = true;
});
}
There's documentation on the Identity Server website that describes how to access protected APIs as seen here. But this is using OpenIdConnect. Since I am using WsFederation, I have no clue on how to get the access token or refresh token. Is token refresh impossible with WsFed?
Can anyone point me in the right direction on how to go about this?

Use OpenIdConnect in the MVC client instead of WsFed. Change the code in the MVC client's Startup.cs to the following:
services.AddAuthentication(options =>{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
}).AddCookie("Cookies").AddOpenIdConnect("oidc", options =>{
options.Authority = "https://localhost:5001";
options.ClientId = "mvc-openid";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.SaveTokens = true;
});
The corresponding client registration in Identity Server should be:
new Client {
ClientId = "mvc-openid",
ClientSecrets = {
new Secret("secret".Sha256())
},
AllowedGrantTypes = GrantTypes.Code,
RedirectUris = {
"https://localhost:6001/signin-oidc"
},
AllowedScopes = new List < string > {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api"
}
}
https://localhost:5001 is the Identity Server address and https://localhost:6001 is the MVC client address.
The access token for accessing the API can be obtained like so:
var accessToken = await HttpContext.GetTokenAsync("access_token");

Related

How to use cookie authentication for Ocelot apigatway

I am using IdentitySever which issue id_token. Their is another website which uses cookie authentication with openidconnect. Code is as below
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// Allow sign in via an OpenId Connect provider like OneLogin
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromHours(12);
options.SlidingExpiration = false;
options.Cookie.Name = "TestApp";
})
.AddOpenIdConnect(options =>
{
options.RequireHttpsMetadata = false;
options.SignInScheme = "Cookies";
options.ClientId = "Test";
options.ClientSecret = "client_sceret";
options.Authority = "http://localhost:57744/identity";
options.ResponseType = "id_token";
options.GetClaimsFromUserInfoEndpoint = true;
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = redirectToIdentityProvider
};
}
);
services.Configure<OidcOptions>(Configuration.GetSection("oidc"));
}
I am creating another Apigateway. If user is logged in website than should be able to make api request through gateway. Both website and gateway will be hosted under same sub-domain While making request to apigateway I will pass cookie created by website in header and want to utilize it at gateway to get claims from identityserver.
Is their way to integrate cookie authentication and identity sever with ocelot gateway?

SignalR authentication with Blazor wasm Bearer Token

I authenticated to Signalr with Bearer token and signalr said Authentication was
successful.
But Signalr Hub Context.Identity.User.Name is null after authentication.
How can i access to authenticated user information in SignalR Hub for connectionid and user mapping.
My Startup.cs code for authentication.
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Authority = "https://security.minima.local";
options.Audience = "blazor_web_app_api";
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new
TokenValidationParameters()
{
ValidateAudience = false
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/notification")))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
})
.AddIdentityServerJwt();
You need to tell the TokenValidator which claim is the name and role claim and you do that by setting:
.AddMyJwtBearer(opt =>
{
...
opt.TokenValidationParameters.RoleClaimType = "roles";
opt.TokenValidationParameters.NameClaimType = "name";

Multiple Azure AD Authentication Identity Server 4

I would like to know if it's possible to setup a .NET Core Application using IdentityServer 4 that can Authenticate to more than one AzureAd configuration.
Currently you can add 1 AzureAD configuration like this:
services.AddAuthentication()
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
But I wanted to be able to Authenticate users from multiple Organisations using AzureAd. So different TenantId...etc
This will have to be done on the fly depending on the organisation chosen in the UI.
How can I accomplish that ?
You can use AddOpenIdConnect middleware :
services.AddAuthentication()
.AddOpenIdConnect("AADTenant1", "AADTenant1", options =>
{
options.ClientId = "<app1>";
options.Authority = "https://login.microsoftonline.com/<tenant1>/";
options.CallbackPath = "/signin-oidc-aadtenant1";
options.SaveTokens = true;
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
})
.AddOpenIdConnect("AADTenant2", "AADTenant2", options =>
{
options.ClientId = "<app2>";
options.Authority = "https://login.microsoftonline.com/<tenant2>/";
options.CallbackPath = "/signin-oidc-aadtenant2";
options.SaveTokens = true;
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
})
And trigger the scheme you want to challenge :
var callbackUrl = Url.Action("ExternalLoginCallback");
var props = new AuthenticationProperties
{
RedirectUri = callbackUrl,
Items =
{
{ "scheme", provider },
{ "returnUrl", returnUrl }
}
};
return Challenge(provider, props);

Why my OAuth provider works incorrectly when I add Identity to a project?

I have code that authenticates an user using OAuth. Here is this code: Github link
I use this code in the ConfigureServices() method of the Startup class:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options=>
{
options.LoginPath = new PathString("/Account/Login");
options.LogoutPath = new PathString("/Account/Logout");
options.AccessDeniedPath = new PathString("/Account/Forbidden");
})
.AddVkontakte(options => // here
{
options.ApiVersion = "5.95";
options.ClientId = Configuration["VKontakte:ClientId"];
options.ClientSecret = Configuration["VKontakte:ClientSecret"];
});
services.AddDefaultIdentity<User>(options =>
{
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
})
.AddEntityFrameworkStores<ApplicationContext>()
.AddDefaultTokenProviders();
services.AddMvc();
}
But when I try to authenticate using it, nothing happens. It works the way I want, only when I remove this strokes
...
services.AddDefaultIdentity<User>(options =>
{
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
})
.AddEntityFrameworkStores<ApplicationContext>()
.AddDefaultTokenProviders();
In both cases, the code behind .AddVkontakte(...) works correctly, I checked it in the network inspector of the browser. My code makes requests to the OAuth provider(vk.com) and successfully gets responses. But I don't understand why AddDefaultIdentity<User>(...) doesn't allow .AddVkontakte(...) to authenticate an user.
What do you think about this?
Okay, I looked at this question (Asp Core 2.1 Jwt + Identity. userManager store does not implement IUserRoleStore), and tried to change a little bit options passed to AddAuthentication, and it worked!
Here is the final code:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options=> // defined some options
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ApplicationScheme;
})
.AddCookie(options=>
{
options.LoginPath = new PathString("/Account/Login");
options.LogoutPath = new PathString("/Account/Logout");
options.AccessDeniedPath = new PathString("/Account/Forbidden");
})
.AddVkontakte(options =>
{
options.ApiVersion = "5.95";
options.ClientId = Configuration["VKontakte:ClientId"];
options.ClientSecret = Configuration["VKontakte:ClientSecret"];
});
services.AddDefaultIdentity<User>(options =>
{
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
})
.AddEntityFrameworkStores<ApplicationContext>()
.AddDefaultTokenProviders();
services.AddMvc();
}
I don't know what does it mean, but it works! Wait and see.

ASP.NET Authorize Filter Denies Access for User in Specified Role

In my ASP.NET Core 2.0 Application, I am stuck with an issue an Admin logged in User cannot access controller I used the Authorize Filter on [Authorize(Policy="AdminAlone")].
I confirmed that the user is in the "Administrators" role and added the policy in startup.cs but it redirects to an AccessDenied view when I try to access the controller.
I saw a similar problem on this link, but the solution didn't help me
Startup Class in MVC Client - ConfigureServices
services.AddMvc();
services.AddSession();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddAuthorization(options =>
{
options.AddPolicy("AdminAlone", policy => policy.RequireRole("Administrators"));
});
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultForbidScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie("Cookies")
.AddOpenIdConnect("Bearer", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ClientId = "mvcWeb";
options.ClientSecret = "spring12345";
options.ResponseType = OidcConstants.ResponseTypes.CodeIdToken;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("NuB.HospitalSearch");
options.Scope.Add("offline_access");
});
Web API ConfigureServices
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler
{
InboundClaimTypeMap = new Dictionary<string, string>()
};
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(option =>
{
option.Audience = "NuB.HospitalSearch";
option.Authority = "http://localhost:5000";
option.RequireHttpsMetadata = false;
option.SecurityTokenValidators.Clear();
option.SecurityTokenValidators.Add(jwtSecurityTokenHandler);
option.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateAudience = true,
ValidAudience = "NuB.HospitalSearch",
ValidateIssuer = true
};
});
You may try the following. Inside your AddOpenIdConnect configuration add
options.TokenValidationParameters = new TokenValidationParameters {
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role
};
In fact, this property defines the types and definitions required for validating a token. Please refer to this post from Dominick Baier for a more detailed explanation.