Its very difficult to ask questions on ID4. Also all relevant discussions I could google point to non-existing links for code samples.
What I have: an angular client uses ID4 auth made up of MVC pages provided by Microsoft, hosted in ASP.NET Core.
Startup.cs
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<AppDbContext>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, AppDbContext>();
services.AddAuthentication()
.AddIdentityServerJwt();
. . .
app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();
authorize.service.ts
const settings: any = await response.json();
settings.automaticSilentRenew = true;
settings.includeIdTokenInSilentRenew = true;
this.userManager = new UserManager(settings);
Use case: a user logs in on the webpage, ticks Remeber Me checkbox and uses the auth'ed website parts. If the server restarts - the tokens lost and user interaction with the website becomes broken - e.g. there are no errors in the webbrowser console and nothing happens if user tries to access any server-side data. It looks like nothing is there. Not as if there is issue with the auth.
It can only be fixed by manually logout/login. Or by opening new tab and going to the app again.
Is there simple explanation on to how can I a) detect that client token is broken and b) request new token?
EDIT if a user continues to use website next working day or opens new browser tab (without server restart) the ID4 works as expected - no login required.
The fiddler:
The short answer is: nothing should break if the server process (either application backend or identityserver4-based IDP) restarts so chances are you're missing some persistence (for persisted grants) and/or shared config (e.g. token signing keys and ASP.Net data protection keys) and things are being regenerated on startup and in-memory data is being lost.
This article covers the things you need to consider to deploy a viable production service: https://docs.identityserver.io/en/latest/topics/deployment.html
With that all in place though tokens will still expire and there are a couple of ways to refresh them depending on the context and grant type in use.
To detect if a token is not valid anymore:
Use it and detect if you get a 401 response from the endpoint you're calling
Check the exp claim inside the token yourself
Use the expires_in value returned with the token and calculate the expiry time based on that
To renew it (and some libs will automate this):
Use the iframe-based silent renewal mechanism (authorize endpoint with prompt=none) - note that third part cookie restrictions come into play for this
Use a refresh token via the token endpoint (not recommended for client side apps due to the need to persist a refresh token in the client side)
Related
I have a asp.net core hosted blazor application using
builder.Services.AddIdentity<IdentityUser, IdentityRole>(options =>
{
// ...
}
.AddEntityFrameworkStores<ApplicationDbContext>()
and
builder.Services.AddIdentityServer()
.AddApiAuthorization<IdentityUser, ApplicationDbContext>()
I want to add a feature, that a user can create an api token with passing an (optional) expiration date, and name.
The created tokens should be revokable.
Ideally the tokens are limited to api endpoints.
Is ServerSideSessions the way to go?
Since IdentityServer is rotating keys, will it be capable of validating tokens that were issued +1 year ago?
The reason for this is, that we have monitoring systems, that monitor data coming from that api. these systems only can issue http calls, no oauth integration.
EDIT
I found https://github.com/DuendeSoftware/Samples/blob/main/IdentityServer/v6/PAT/src/IdentityServer/Pages/PAT/Index.cshtml.cs
It seems like I can create Tokens with a Lifetime with Injecable ITokenService.
Still i didn't find a default way to revoke tokens.
I think IdentityServer is not the right tool to handle API Tokens. What IdentityServer can handle is Oauth Clients and their secrets but that just allowed an oauth capable client to create a token that can be directly used on an API endpoint. Server side session just stores the user data on the server side not in a cookie so this is not the right option.
In my opinion the right way to handle that is to create a custom AuthenticationHandler maybe similar to this:
https://rmauro.dev/api-key-authentication-extending-the-native-implementation/
In there you can get access to a dbcontext for example where the API Tokens are stored and you can validate them.
You can then use AddPolicyScheme to switch between either the apiauthentication or your custom scheme.
https://code-maze.com/dotnet-multiple-authentication-schemes/
Probably something like this:
builder.Services.AddAuthentication( x=> x.DefaultScheme == "TOKEN_OR_JWT")
.AddIdentityServerJwt()
.AddScheme<YourCustomHandler>("YOUR-CUSTOM-SCHEME")
.AddPolicyScheme("TOKEN_OR_JWT", "TOKEN_OR_JWT", options =>
{
// runs on each request
options.ForwardDefaultSelector = context =>
{
// check if an API-TOKEN header is present
string? apiToken = context.Request.Headers["API-TOKEN"];
if (!string.IsNullOrEmpty(apiToken))
return "YOUR-CUSTOM-SCHEME";
// otherwise always use the jwt scheme for identityserver
return IdentityServerJwtConstants.IdentityServerJwtScheme;
};
});
I have one API and one Client app (Blazor web assembly) separately.
The client app authenticates users through the Azure active directory. In the client when I have saved any file through API, I am using static files configured on Startup.cs such as :
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new PhysicalFileProvider(Path.Combine(#"c:\", #"Resources")),
RequestPath = new PathString("/secureFile"),
});
The problem is the client application is working fine. But if someone inspects the Html and gets the image source they can directly access the files through the browser like https://localhost:44397/secureFile/profile.jpg
On the other hand, because I am using Azure active directory login, the API does not save any user information. That's why I can't use the scenario like:
app.UseAuthentication();
app.UseAuthorization();
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new PhysicalFileProvider(Path.Combine(#"c:\", #"Resources")),
RequestPath = new PathString("/secureFile"),
OnPrepareResponse = ctx =>
{
if (!ctx.Context.User.Identity.IsAuthenticated)
{
// respond HTTP 401 Unauthorized.
ctx.Context.Response.StatusCode = (int) HttpStatusCode.Unauthorized;
ctx.Context.Response.ContentLength = 0;
ctx.Context.Response.Body = Stream.Null;
// ctx.Context.Response.Redirect("/")
}
}
});
Here the !ctx.Context.User.Identity.IsAuthenticated is always false even the client application is calling.
May Client application is Authenticating though :
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Audience = Configuration["AAD:ResourceId"];
options.Authority = $"{Configuration["AAD:InstanceId"]}{Configuration["AAD:TenantId"]}";
});
My goal is to give access to the https://localhost:44397/secureFile/profile.jpg if the client application is calling the image source. But should block if anyone calls through the URL directly.
Any lead will help me a lot.
I have already followed article but no use in my scenario because I am using AAD login.
You mention that "the API does not save any user information," but regardless of the authentication flow, some authentication information has to be established even transiently for the individual request so that authorization can be evaluated.
In the example code you have listed, you're invoking a check on the HttpContext User claims principal to see if the user is authenticated, and you mention it's always coming back as false, even if it's coming from your client application. What's notable here is that something has to invoke the authentication flow in an application before authorization can be performed. Because nothing in the StaticFileMiddleware inherently does this, it probably never is happening during that request.
You can invoke that authentication flow yourself through the use of the ChallengeAsync() method, which is intended to "challenge the current request using the default challenge scheme. An authentication challenge can be issued when an unauthenticated user requests an endpoint that requires authentication":
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.authenticationhttpcontextextensions.challengeasync?view=aspnetcore-6.0
You may need to customize the behavior for your particular case, especially if you have to accommodate multiple schemes, but a general starting point would be adding a using Microsoft.AspNetCore.Authentication; to ensure the HttpContext extensions are available, then adding a corresponding call to await ctx.Context.ChallengeAsync() within your flow if the user is not authenticated.
Once that runs, you can check again whether the user is both authenticated and whether they meet the authorization requirements you would like to have for your secure static files.
Security is one of those things that once you set it up, you kinda forget about it, until something doesn't work. I have a .Net Core application that uses OpenId Connect with Azure. The sign in works fine, but we noticed a few days ago that the signout wasn't working. You could sign out of the application, but then login again directly without entering credentials. So, I have been looking around and found out that it's not good enough to clear the cookies and session, you need to go to the "end_session_endpoint" to actually clear the credentials. I have looked in a number of places, but I can't find a simple code example of how to to this. And the code examples I have tried don't seem to work. When I run locally, I can logout and it says it logged me out, but when I start the application again, I am logged in automatically right away. I understand the concept, I just don't know how to do it. Below is what my authentication looks like in the Startup.cs file:
services.AddAuthentication(options =>
{
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.Authority = Configuration["Authentication:Microsoft:OAuth"];
options.RequireHttpsMetadata = true;
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.UsePkce = false;
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("email");
options.SaveTokens = true;
options.CallbackPath = new PathString(Configuration["Authentication:Microsoft:Callback"]);
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
};
// MetadataAddress represents the Active Directory instance used to authenticate users.
options.MetadataAddress = Configuration["Authentication:Microsoft:Meta"];
options.ClientId = Configuration["Authentication:Microsoft:ApplicationId"];
options.ClientSecret = Configuration["Authentication:Microsoft:Password"];
});
Does anyone have a straight forward code example for this?
RP INITIATED LOGOUT
This is mostly a case of sending the standard message with these parameters.If I remember rightly, Azure may require the third of these to log you out successfully.
client_id
post_logout_redirect_uri
id_token_hint
EXAMPLES
Here is a C# example that sends this type of request. It uses the older .Net framework, but should be easy to follow.
In .Net Core, you can set a SignedOutCallbackPath in the OIDC properties, which must match a post_logout_redirect_uri registered against the client in Azure AD. You can then call this code in the controller and it should send the standard mesaage.
await HttpContext.SignoutAsync():
Personally I find this .NET syntax and C# layer a little unintuitive, but ultimately it is just a wrapper around the standard messages.
My Azure SPA example does an RP initiated logout, and I can confirm that the above parameters work fine with Azure AD.
ADVANCED OPTIONS
Sometimes logout can be a very tricky area to get the best usability behaviour, depending on the provider. In case useful later, another option is to use the max-age parameter from OpenID Connect during sign in. You then get an auth_time claim in the ID token. On the next redirect after auth_time has expired you can use this data to send a prompt=login parameter if required, to force the user to re-authenticate. The .NET way to do this is described in this post.
UPDATE
Here is what my SPA config looks like, with the post logout redirect URI, to check against your own configuration. RP (relying party) is your C# web app, OP (openid connect provider) is Azure AD (I never liked this type of terminology). The logout mechanics are just focused on removing the SSO cookie that Azure AD sets. Azure AD logout behaviour may not be easy to control fully though, since logout in OAuth technologies can sometimes get complex and involve multiple apps.
TEST OPTION
One option you could try is logging in and out using OAuth Tools, which is useful for troubleshooting this type of problem without needing to write code. Once you get the correct behaviour here, you'll be clearer about what you need to code:
First find your OpenID Connect metadata URL - this is my URL
Next select Add Environment, give it a name like my and enter the metadata URL - then make sure this environment is selected
Add https://oauth.tools/callback/code to Azure AD as a redirect URI
Then select Code Flow in OAuth tools, enter your development Azure AD client ID and client secret, then select Run, to trigger a redirect
Then scroll down and click Redeem Authorization Code, which will get tokens, including an ID token which might be needed for logout
Then run a Logout Flow, for which you'll need to set https://oauth.tools in Azure AD as the front channel logout URL
If you can't get logout to work as you'd like, try the prompt=login option, which will force a new login
Q & A
So OIDC logout is working but a second factor time to live is not. This is outside the OIDC specs and probably cannot be influenced by code. Try it in a new Incognito browser window and see if you have to enter the second factor. If so then it will be a cookie that you may be able to identify if you trace the HTTP traffic. Look for a configuration setting somewhere that controls this - something similar to the Curity TTL setting in this article. If there is no such setting then it may be outside your control.
Leftover browser sessions. In some ways if users let other people use their desktop sessions then all bets are off. You can get too hung up on this. Just aim to be as good as other apps. Eg are you as secure as Office 365 itself, Gmail etc?
By default (without a post logout redirect URI) the user is dumped back in the Azure login screen, which is typically not what the user wants. Instead a post logout landing screen gives your app control over the UX. A common option is to just display something like You are signed out, click here to log back in. In a C# website this would just return a view to the browser.
I need help. Program WebApplication Code C# Asp.Net
First ,I've implemented a way to put a URI in Google OAuth 2 authorization.
Download the Json file for the program to read the file and generate the token.
Error
Failed to launch browser with "https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&response_type=code&client_id=879100301665-3hpkd1h812d5eejji8o4mku3c2ci3rcs.apps.googleusercontent.com&redirect_uri=http%3A%2F%2F127.0.0.1%3A63526%2Fauthorize%2F&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fgmail.send" for authorization. See inner exception for details.
Second , I used to put values in Programs by assigning Client ID and Client secret in Google OAuth 2 authorization.
Error
Access to the path 'C:\Windows\system32\config\systemprofile' is denied
There are a number of issues with what you are doing.
First being that you have created what appears to be a web application credentials on Google cloud console. However the code you are using GoogleWebAuthorizationBroker.AuthorizeAsync is intended for use with installed applications only. This is why you are getting the error you are getting.
The code for creating an asp .net core web app is as follows
public void ConfigureServices(IServiceCollection services)
{
...
// This configures Google.Apis.Auth.AspNetCore3 for use in this app.
services
.AddAuthentication(o =>
{
// This forces challenge results to be handled by Google OpenID Handler, so there's no
// need to add an AccountController that emits challenges for Login.
o.DefaultChallengeScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme;
// This forces forbid results to be handled by Google OpenID Handler, which checks if
// extra scopes are required and does automatic incremental auth.
o.DefaultForbidScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme;
// Default scheme that will handle everything else.
// Once a user is authenticated, the OAuth2 token info is stored in cookies.
o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddGoogleOpenIdConnect(options =>
{
options.ClientId = {YOUR_CLIENT_ID};
options.ClientSecret = {YOUR_CLIENT_SECRET};
});
}
The second issue you are having is that the redirect uri you are adding is not a valid URL it needs to be https:// at the very least you are missing the //. As well as the fact that the client library will be sending the request using /signin-google
So a proper redirect uri should be more like this.
https://localhost:5001/signin-google
I have a YouTube video which shows How to get a Google users profile information, with C#. with a companion blog post Asp .net core 3 and Google login both should work fine with .net 5 as well.
I have been trying to wrap my head around this concept but have many questions and unfortunately, all official documents and tutorials are based on Visual Studio templates with individual user accounts.
My goal is pretty straightforward I believe. I have a web application which will only support external provider logins (namely: Facebook, Twitter, and LinkedIn). I do not want to support cookie authentication since there won't be a support for custom username/password.
My first problem is to define a default AuthenticationScheme. Below is my startup.cs:
services.AddAuthentication()
.AddFacebook(/* options */)
.AddTwitter(/* options */)
If I define a controller action with Authorize attribute I get no default authentication scheme defined error when I hit that route. However, I want users to be redirected to my login route if they are unauthorized. If I modify startup.cs like below it all works but then I think I support cookie (old forms authentication?) authentication which I don't want to.
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie()
.AddFacebook(/* options */)
My other issue is that I don't know what happens under the hood of AddFacebook() call. If I set up my middleware this way and log in with Facebook I magically get all the necessary tokens, claims and suddenly I have an application cookie set and my fb_login callback route can access to Facebook's token! When I check the network requests I see there is a hit to the signin-facebook route -which I didn't define- and I guess under the hood it calls HttpContext.SignInAsync() etc... but if I refresh my fb-login callback and check if
HttpContext.AuthenticateAsync(FacebookDefaults.AuthenticationScheme)
returns Success = true no! it returns false! But it was true just one second ago?
Also, when should I be using methods like AuthenticateAsync() and SignInAsync()?
Long story short I need a tutorial or documentation that explains this middleware without asp.net Identity framework, EntityFramework and templates.
I want to understand how a simple AddFacebook() call binds everything, and if I want to manually do that (say with AddOauth) how can I achieve the same functionality?
I'm not a fan of "automagically working" code so if someone can explain what's going on here I'd be very appreciated.
Cookie auth is used to persist the authenticated state between the requests. There is no substitute for this, and no it's not the same as forms auth, though cookies are used in both cases. The reason for that is simply that cookies are what makes state work over the HTTP protocol, which is itself stateless. If exclude the use of cookies, then there is no other mechanism to maintain state.
Using something like the Facebook auth scheme directly authorizes the initial request, but again, because there is no state, the next request is no longer authenticated without going through the Facebook OAuth flow again.
Long and short, the other auth schemes are there for things like APIs, where each request is typically authenticated individually via something like the Authorization header. A web browser doesn't work this way, and instead relies on cookies handle subsequent authorization. No cookies, no auth.