IdentityServer4 and integration with signinmanager - authentication

We are working on an application that uses IdentityServer4, a web api endpoint, a web administration front end, and a mobile application. We have the web application set up like this
public void ConfigureServices(IServiceCollection services) {
services.AddMvc();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services
.AddAuthentication(options => {
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options => {
options.SignInScheme = "Cookies";
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.ClientSecret = "secret";
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("api1");
options.Scope.Add("offline_access");
});
}
This works and when we call into a controller that has an [Authorize] attribute on it, we get redirected, sign in, return to the calling site, and all is well.
Now we are trying to add authorization into the mix. I've seen the video where it is shown not to put authorization information into the token and I get that conceptually. But now I'd like to do something simple in the UI, like show or hide a link based on if the user is signed in or not. The default _LoginPartial page has something like this:
#inject SignInManager<User> SignInManager
#inject UserManager<User> UserManager
#if (SignInManager.IsSignedIn(User)) {
User is signed in
} else {
User is anonymous
}
In order to use SignInManager and UserManager, we'd have to set up the dependency service:
services.AddDbContext<Entities.ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<User, IdentityRole<long>>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
But when I do that, it messes up the authorization attribute on the home controller.
How do I, using IdentityServer4, go about testing whether a user is logged in or not and access my role storage in the web application? I must be missing something that is allowing us to externally authenticate and then turn around and figure out what roles / claims the user is able to be in / use.

Use things like User.Identity.IsAuthenticated and User.Identity.IsInRole, etc. instead. While you can back IdentityServer with ASP.NET Identity, it's important to realize that your MVC app is using IdentityServer, not Identity, for authentication and authorization. As such, things like SignInManager.IsSignedIn won't work.

Related

Can I use openid connect authentication and jwtbearer authentication scheme in one app?

I have an ASP.NET Core web app where the front-end is built in react and for the back-end, I have web APIs, both the things are placed in one web project.
for authentication, I used Azure AD open Id connect authentication scheme.
Now I have a requirement where I need to expose some APIs to an external system using client credentials flow. so I am not sure how to implement this in the current web app as it already has the open id connect authentication scheme.
Any idea?
Many thanks
You need to create two applications in Azure portal. At present, you already have an ASP.NET Core Web application, which represents the front-end application, so you need to create another application that represents the Web API, that is, the back-end application.
First, you need to expose the api of the back-end application protected by Azure, which can be configured according to the following process:
Azure portal>App registrations>Expose an API>Add a scope>Add a client application
Then you need to create the appRole of the back-end application, and then grant that role as an application permission to the client application.
Next, go to client application>API permissions>Add a permission>My APIs>your api application.
Finally, you need to obtain an access token using the client credential flow where no user is logged in:
Parse the token:
Finally, you can pass the token to the api application, and the api application will authenticate the client application by parsing the token.
Similar samples.
I had the same case and solved it this way:
Startup.cs
string[] scopes = new[]
{
"openid",
"profile",
"offline_access",
"email",
"scope_one",
"scope_two",
};
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://my-own-indentity.com";
options.Audience = "Any_Audience";
options.RequireHttpsMetadata = false;
options.SaveToken = true;
}) .AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = "https://Authority.com";
options.ClientId = "*****";
options.ClientSecret = "******";
options.CallbackPath = "/callback";
options.ResponseType = "code";
options.ResponseType = OidcConstants.ResponseTypes.Code;
options.RequireHttpsMetadata = false;
options.SaveTokens = true;
options.Scope.Clear();
options.GetClaimsFromUserInfoEndpoint = true;
foreach (string scope in scopes)
{
options.Scope.Add(scope);
}
});
To be authenticated by "AddJwtBearer" for controller:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Route("[controller]")]
[ApiController]
public class ProjectController : ControllerBase
{
//your code
}
To be authenticated by "AddOpenIdConnect" for controller::
[Authorize(AuthenticationSchemes = "oidc")]
[ApiController]
[Route("api/[controller]")]
public class SomethingWithOpenIdController : Controller
{
//your code
}
If you found another solution feel free to share, otherwise I hope this helps you.
In Startup.cs
// Add services to the container.
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, (options) =>
{
options.TokenValidationParameters.ValidateIssuer = true;
options.MetadataAddress = "metadataaddress";
options.TokenValidationParameters.ValidAudience = "portal.com";
// or any specific option used for your application
});
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(
options =>
{
builder.Configuration.Bind("AzureAd", options);
// or any specific option used for your application
});
In Controllers:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme + "," + OpenIdConnectDefaults.AuthenticationScheme)]
[ApiController]
[Route("[controller]")]
public class SomeController : ControllerBase
This worked for me :)

How to support Windows authentication and OpenId in an ASP.NET Core 5 Web API?

I have a Web API that currently supports Windows authentication out-of-the-box. In my Startup.cs I simply have:
services.AddAuthentication();
services.AddAuthorization();
and:
app.UseAuthentication();
app.UseAuthorization();
That's all there is for now. Both Windows and anonymous authentications are enabled and all controllers have the [Authorize] unless the require anonymous. Most controllers have CRUD actions and each on each action I have a custom filter that checks against the DB if a user is allowed to perform the action.
This API serves a Winforms desktop app and users login with their domain account. So currently I pass their network credentials on each request, plus an extra token which I use to compare with their database user rights.
Now I have a requirement to add additional authentication schemes, starting with OpenId. I already have a second app that gets an access token from the auth server.
I've started with the following but it's not working:
services.AddAuthentication()
.AddNegotiate(IISDefaults.AuthenticationScheme, options =>
{
})
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.Authority = Configuration["Issuer"];
options.ClientId = Configuration["ClientId"];
options.ClientSecret = Configuration["ClientSecret"];
options.TokenValidationParameters = new TokenValidationParameters {
ValidateIssuer = true,
IssuerSigningKey = new SymmetricSecurityKey(Convert.FromBase64String(Configuration["EncryptionKey"]))
};
});
services.AddAuthorization(options =>
{
var builder = new AuthorizationPolicyBuilder(
IISDefaults.AuthenticationScheme,
OpenIdConnectDefaults.AuthenticationScheme
);
builder.RequireAuthenticatedUser();
options.DefaultPolicy = builder.Build();
});
What am I missing?
Where is your default authentication schema?
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o => ...

ASP.NET Core 3.1 Use both OpenIDConnect and Custom Cookie Authentication

I have an existing application that makes use of Cookie Authentication, and would like to add the ability to authenticate users using Active Directory. The current application uses Cookie based authentication and custom authorisation - roles in a database.
I am adding bits from example located here:
Add sign-in with Microsoft to an ASP.NET Core web app
When I run the application I get an error:
System.InvalidOperationException: Scheme already exists: Cookies
What is the correct way to configure OpenIdConnect and Cookie Authentication.
// STEP 1 Basic Cookie Auth
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Auth";
options.AccessDeniedPath = "/Home/AccessDenied";
options.Cookie.IsEssential = true;
options.SlidingExpiration = true;
options.ExpireTimeSpan = TimeSpan.FromSeconds(day/2.0);
options.Cookie.HttpOnly = true; // not accessible via JavaScript
options.Cookie.Name = "login_token";
options.TicketDataFormat = new CustomJwtDataFormat(
SecurityAlgorithms.HmacSha256,
tokenValidationParameters);
});
// STEP 2 OpenID Connect Auth
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"), "OpenIdConnect", "Cookies", true);
I am not able to find any examples using both Cookie Authentication and OpenID Connect. Is this possible? Allowing users to login selectively using Active Directory authentication, or local authentication (details stored in local database).
After changing the "Cookie" name, get's rid of the error message,
but breaks the local authorisation, e.g.
When a valid Username and Password is given, I typically
authorise the login.
HttpContext.Response.Cookies.Append("login_token", token, GetCookieOptions());
Currently with OpenIDConnect configured User.Identity.IsAuthenticated
remains false.
According to the error messages, it tell you that you have multiple Scheme which named cookies.
According to the AddMicrosoftIdentityWebApp Method document, you could find the third parameter name is the cookieScheme.
The cookie-based scheme name to be used. By default it uses "Cookies".
But you have already set this name at above, so you should use other one. For example: "ADCookies".
Like below:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"), "OpenIdConnect", "ADCookies", true);
The mixed approach is a minefield but the below is allowing use to Authenticate Users via IdentityServer4 using OIDC while authenticating the Application into AzureAD with Identity.Web to get tokens for Api calls.
services.AddAuthentication(options =>
{
options.DefaultScheme = "IS4Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("IS4Cookies")
.AddOpenIdConnect("oidc", "OpenID Connect", options =>
{
options.SignInScheme = "IS4Cookies";
// Get IdentityServer configuration from appsettings.json.
var config = Configuration.GetSection("IdentityServerOptions").Get<IdentityServerOptions>();
options.Authority = config.Authority;
options.RequireHttpsMetadata = false;
options.ClientId = config.ClientId;
options.ClientSecret = config.ClientSecret;
options.ResponseType = "code";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.ClaimActions.MapJsonKey("role", "role");
options.ClaimActions.MapJsonKey("role", System.Security.Claims.ClaimTypes.Role);
options.ClaimActions.MapJsonKey("email", "email");
options.ClaimActions.MapJsonKey("preferred_username", "preferred_username");
options.Events = new OpenIdConnectEvents
{
OnRemoteFailure = context =>
{
context.Response.Redirect("/");
context.HandleResponse();
return Task.FromResult(0);
}
};
})
.AddMicrosoftIdentityWebApp(Configuration, "AzureOptions")
.EnableTokenAcquisitionToCallDownstreamApi(new string[]{"sms.all" })
.AddInMemoryTokenCaches();
This is what I use and it works, you just need to specify the configureCookieAuthenticationOptions and set the name inside there and you should be good to go, also I had to use lax for SameSite or it would not work for me.
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(identityOptions =>
/* {identityOptions.ClientId ="";}, // if you want to specify the options manually instead of Configuration.GetSection() call*/
Configuration.GetSection("AzureAd"),
configureCookieAuthenticationOptions: authCookie => { // Setup SSO cookie
authCookie.Cookie.Name ="Your.Cookie.Name.Here";// change name to hide .net identifiers in name
authCookie.Cookie.HttpOnly = true;// make so client cannot alter cookie
authCookie.Cookie.SecurePolicy = CookieSecurePolicy.Always;// require https
authCookie.Cookie.SameSite = SameSiteMode.Lax;// from external resource
// verify options are valid or throw exception
authCookie.Validate();
}
);
You may or may not need all of the authCookie values here, but it should get you started in the right direction!
It's possible to mix two mechanisms.
I use MicrosoftIdentity authentication for access to administration web pages and cookies authentication for my APIs and SignalR hubs.
I use this in startup ConfigureServices
services
.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie("CookiesApiScheme", options =>
{
options.SlidingExpiration = true;
// There is no redirection to a login page for APIs and SignalR Hubs, I just made a call to /Api/Login/SignIn with credential
options.AccessDeniedPath = new PathString("/Api/Login/AccessDenied"); // Action who just returns an Unauthorized
})
.AddMicrosoftIdentityWebApp(Configuration); // By default scheme is "CookieAuthenticationDefaults.AuthenticationScheme"
And in API controller you can use something like this
[Route("api/[controller]")]
[ApiController]
[Authorize(Roles = Roles.ADMIN)]
[Authorize(AuthenticationSchemes = "CookiesApiScheme")]
public class DefaultController : ControllerBase
{
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
Based on this post: ASP.NET Core 2.0 AzureAD Authentication not working

IdentityServer4 Register from a Client Application

I have created an OAUth server using IdentityServer4 together with .Net Core Identity. It works fine for the most part. The Startup code is as follows:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = Configuration["Settings:OAuthAddress"];
options.ClientId = "razor_code";
options.ClientSecret = Configuration["Settings:ClientSecret"];
options.ResponseType = "id_token code";
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("myapi");
options.Scope.Add("offline_access");
options.Scope.Add("email");
options.Scope.Add("profile");
options.SaveTokens = true;
});
services.AddAuthorization();
...
}
If I use the [Authorize] attribute on a page in my client site, it will automatically send the user to the OAUth server to login. What I would like to do is to be able to use the Register and Login buttons to login the client app as well. I figured out the Login by creating a Login page as follows:
[Authorize]
public class LoginModel : PageModel
{
public IActionResult OnGetAsync()
{
return LocalRedirect("/");
}
}
If a link points to this page, the [Authorize] attribute will cause it to trigger a login on the OAuth server. Does anyone know a better way to trigger a login? My solution always returns to the root page of the site. This will do, since the root page on this app is the only one without the [Authorize] attribute. In the future that could change.
I also want the Register link to work as well. I found this https://openid.net/specs/openid-connect-prompt-create-1_0.html which indicates that the ability to go straight to the Registration page is at least in the works. Does anyone know how to make this happen using Microsoft.AspNetCore.Authentication.OpeIdConnect?
I know this is technically two questions, but I am thinking the answers are related.

Using OpenIdConnect and JwtBearer authentication together

My ASP.NET Core 2.2 web site uses OpenIdConnect to connect to an external OIDC provider for authentication. So we get a redirect to the provider, you log in, it returns back to the site. All of this is handled server side and works great.
Our web site uses javascript to communicate to the API backend, which is decorated with an [Authorize] attribute.
Now, we want to share our API with another application. I am having trouble understanding how to make it work with the existing OpenIdConnect provider. If I use Postman to make a call, I add a Bearer token but I am returned the log in screen for my OIDC provider. I understand this, it's set up for OIDC.
I have tried using AddJwtBearer with [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] set on my Controller and when I do that I can successfully pass in a Bearer token and call my API from Postman but I can't make a call from a javascript page on my site.
So the ultimate question is, how do I configure this site so that I can call an API from my site itself with OIDC and also call from an external app using a Bearer token?
The code looks like this:
public void ConfigureServices(IServiceCollection services)
{
var authbuilder = services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
});
authbuilder.AddCookie(p =>
{
p.SlidingExpiration = true;
p.Events.OnSigningIn = (context) =>
{
context.CookieOptions.Expires = DateTimeOffset.UtcNow.AddHours(14);
return Task.CompletedTask;
};
});
authbuilder.AddOpenIdConnect(options =>
{
options.Authority = Configuration["OpenIdConnectSettings:AuthorityUrl"];
options.ClientSecret = Configuration["OpenIdConnectSettings:ClientSecret"];
options.ClientId = Configuration["OpenIdConnectSettings:ClientId"];
}
options.SaveTokens = true;
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.GetClaimsFromUserInfoEndpoint = true;
options.Events = new OpenIdConnectEvents
{
OnTokenValidated = context =>
{
// validation logic omitted
}
EDIT: Any external apps will also use the same OIDC provider. It's just a matter of getting my API Controllers to work from the web site itself and being called from another app that uses the same OIDC provider.
EDIT: I think I may have gotten it to work by adding this code:
services.AddAuthorization(options =>
{
var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
JwtBearerDefaults.AuthenticationScheme, OpenIdConnectDefaults.AuthenticationScheme);
defaultAuthorizationPolicyBuilder = defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});
if the other application is a service/application (not a human facing client) then that application needs to register as a client in your OIDC provider and then preferably use the client credentials flow, to get a token from your OIDC provider and pass it to the API.
The API only trusts access tokens from your OIDC provider (the token issuer), so the application needs to authenticate against the app and to get a proper access token.