I have integrated Identity server in my system and now I want to preserve Identity server token so that user do not have to re login after browser close.
Here is my code.
//Identity server
if (bool.Parse(_appConfiguration["IdentityServer:IsEnabled"]))
{
IdentityServerRegistrar.Register(services, _appConfiguration);
//BHARAT : JWT Authentication Middleware
services.AddAuthentication().AddIdentityServerAuthentication("IdentityBearer", options =>
{
options.Authority = _appConfiguration["App:ServerRootAddress"];
options.RequireHttpsMetadata = false;
options.ApiName = "default-api";
options.ApiSecret = "def2edf7-5d42-4edc-a84a-30136c340e13".Sha256();
options.CacheDuration = TimeSpan.FromMinutes(30);
options.SaveToken = true;
});
}
I tried to follow this
https://github.com/IdentityServer/IdentityServer3/issues/1379
This is snap of tokens in browser.
I have tried many other way to preserve the cookie/token but it's not working.
Do I have to add anything in my client side, I am using .net core 2.0 with angular version.
Try changing the condition in login.service.ts:
// Before
var tokenExpireDate = rememberMe
? (new Date(new Date().getTime() + 1000 * expireInSeconds))
: undefined;
// After
var tokenExpireDate = expireInSeconds
? (new Date(new Date().getTime() + 1000 * expireInSeconds))
: undefined;
Related
i think the correct place is in SecurityTokenValidated but account is always null. i dont know how to set up the graphclient here?
SecurityTokenValidated = async (x) =>
{
IConfidentialClientApplication clientApp2 = MsalAppBuilder.BuildConfidentialClientApplication();
AuthenticationResult result2 = null;
var account = await clientApp2.GetAccountAsync(ClaimsPrincipal.Current.GetMsalAccountId());
string[] scopes = { "User.Read" };
// try to get an already cached token
result2 = await clientApp2.AcquireTokenSilent(scopes, account).ExecuteAsync().ConfigureAwait(false);
var graphClient = new GraphServiceClient(
new DelegateAuthenticationProvider(async (request) =>
{
//var token = await tokenAcquisition
// .GetAccessTokenForUserAsync(GraphConstants.Scopes, user: context.Principal);
var token = result2.AccessToken;
request.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", token);
})
);
var user = await graphClient.Me.Request()
.Select(u => new
{
u.DisplayName,
u.Mail,
u.UserPrincipalName
})
.GetAsync();
var identity = x.AuthenticationTicket.Identity;
identity.AddClaim(new Claim(ClaimTypes.Role, "test"));
}
Please refer to this sample: https://learn.microsoft.com/en-us/samples/azure-samples/active-directory-dotnet-admin-restricted-scopes-v2/active-directory-dotnet-admin-restricted-scopes-v2/
You could follow this sample to get access token with GetGraphAccessToken() and make sure the signed-in user is a user account in your Azure AD tenant. Last thing is using Chrome in incognito mode this helps ensure that the session cookie does not get in the way by automatically logging you in and bypassing authentication.
This sample will not work with a Microsoft account (formerly Windows
Live account). Therefore, if you signed in to the Azure portal with a
Microsoft account and have never created a user account in your
directory before, you need to do that now. You need to have at least
one account which is a directory administrator to test the features
which require an administrator to consent.
var graphserviceClient = new GraphServiceClient(
new DelegateAuthenticationProvider(
(requestMessage) =>
{
// Get a token for the Microsoft Graph
var access_token = await GetGraphAccessToken();
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", access_token);
return Task.FromResult(0);
}));
}
private async Task<string> GetGraphAccessToken()
{
IConfidentialClientApplication cc = MsalAppBuilder.BuildConfidentialClientApplication();
var userAccount = await cc.GetAccountAsync(ClaimsPrincipal.Current.GetMsalAccountId());
AuthenticationResult result = await cc.AcquireTokenSilent(new string[] { "user.read" }, userAccount).ExecuteAsync();
return result.AccessToken;
}
I'm using OIDC client and I'm calling below line to siginin,
await this.userManager.signinRedirect(this.createArguments(state));
return this.redirect();
after this I see in the network tab it is navigated to:
https://localhost:5001/connect/authorize?client_id=WebPriorTrainingAuth&redirect_uri=https%3A%2F%2Flocalhost%3A5001%2Fauthentication%2Flogin-callback&response_type=code&scope=openid%20profile&state=9a061d073a424b76bfee25c9bad535d4&code_challenge=ElP_Qtwl8skk13ZyhkzWbnQqU04Y_xYAQXN09cyLY_E&code_challenge_method=S256&response_mode=query
with an error message:
error:invalid_request
error_description:The specified 'redirect_uri' is not valid for this client application.
error_uri:https://documentation.openiddict.com/errors/ID2043
This should have redirected to /Account/Login page (https://localhost:5001/Account/Login?ReturnUrl=%2Fconnect%2) I guess, but that is not happening.
Can someone pls help on this?
In the Authorizationcontroller, the client parameters will have the below value set.
var result = new Dictionary<string, string>();
var application = await applicationManager.FindByClientIdAsync(clientId, cancellationToken);
if (application != null)
{
result.Add("authority", httpContext.GetBaseUrl());
result.Add("client_id", application.ClientId);
result.Add("redirect_uri", "https://localhost:5001/authentication/login-callback");
result.Add("post_logout_redirect_uri", "https://localhost:5001/authentication/logout-callback");
result.Add("response_type", "code");
result.Add("scope", $"openid profile");
//result.Add("response_mode", "query");
}
return result;
In the startup.cs, the below code for OpenIddict settings,
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.LoginPath = "/Identity/Account/Login";
options.LogoutPath = "/Identity/Account/Logout";
})
.AddOpenIdConnect(options =>
{
options.SignInScheme = "Cookies";
options.ForwardSignIn = "Cookies";
options.Authority = baseUrl;
options.SignedOutRedirectUri = baseUrl;
options.ClientId = AuthenticationClient.WebClientId;
options.RequireHttpsMetadata = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.UsePkce = true;
/// Use the authorization code flow.
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;
options.Scope.Add(Scopes.OpenId);
options.Scope.Add(Scopes.Profile);
options.Scope.Add(AuthenticationClient.WebClientApiScope);
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";
options.Events = new OpenIdConnectEvents
{
/// Add Code Challange
OnRedirectToIdentityProvider = context =>
{
/// Set ProjectId
context.ProtocolMessage.SetParameter("project_id", context.HttpContext.User.Identity.Name);
/// Only modify requests to the authorization endpoint
if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication)
{
/// Generate code_verifier
var codeVerifier = CryptoRandom.CreateUniqueId(32);
/// Store codeVerifier for later use
context.Properties.Items.Add("code_verifier", codeVerifier);
/// Create code_challenge
string codeChallenge;
using (var sha256 = SHA256.Create())
{
var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier));
codeChallenge = Base64Url.Encode(challengeBytes);
}
/// Add code_challenge and code_challenge_method to request
context.ProtocolMessage.Parameters.Add("code_challenge", codeChallenge);
context.ProtocolMessage.Parameters.Add("code_challenge_method", "S256");
}
return Task.CompletedTask;
},
Can some one pls tell me why the signinredirect call is not redirecting to /Account/Login page?
This error is returned when the specified redirect_uri is not recognized by OpenIddict.
Are you sure you added https://localhost:5001/authentication/login-callback to the list of allowed redirect_uris for your WebPriorTrainingAuth client?
I think the redirect URL should be to the Callbackpath of the OpenIDConnect handler in the ASP.NET core client. This path is by default set to:
CallbackPath = new PathString("/signin-oidc");
This is the path where the autorization code is sent to after a successfull authentication in IdentityServer.
See the source code here:
I know this is an old question and already answered .. and this answer not for this case.
But you are a new user getting this error message and you are working on 127.0.0.1 .... please make sure that your OpenIddictApplication has localhost AND 127.0.0.1 as valid rediect urls in RedirectUris list.
Iam using an identityServer4 with multiple Databases. I could so far use muliple databases with the user-store. I solve it as the follwoing code of a middleware:
public async Task Invoke(HttpContext context)
{
var req = context.Request;
if (req.Path == "/Account/Login" && req.Method.Equals("POST"))
{
if (req.Form.Keys.Contains("Input.Database") == false)
{
throw new InvalidOperationException("No database key was sent with this request: " + req.Path);
}
var lDatabasKey = req.Form["Input.Database"];
_configuration["ConnectionStrings:default"] = _configuration[$"ConnectionStrings:{lDatabasKey}"];
}
the configuration Object get updated, so the value ConnectionString:default get updated with the connectionstring i need to use.
This concept unfortunatley not working the the configuration database, which inistalised in the ConfigureServices in Startup.cs:
string connectionString = Configuration.GetConnectionString("default");
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.SignIn.RequireConfirmedEmail = false;
})
.AddEntityFrameworkStores<IdentityUserDbContext>()
.AddDefaultTokenProviders().AddClaimsPrincipalFactory<CentralHubClaimsPrincipalFactory>();
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
options.UserInteraction.LoginUrl = "/Account/Login";
options.UserInteraction.LogoutUrl = "/Account/Logout";
options.Authentication = new AuthenticationOptions()
{
CookieLifetime = TimeSpan.FromHours(10), // ID server cookie timeout set to 10 hours
CookieSlidingExpiration = true
};
})
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
}).AddAspNetIdentity<ApplicationUser>()
.AddProfileService<AspNetIdentityProfileService>();
using breaking point i can see that the connectionString:default in the middleware has correct value of the database i want to use. but it still uses the default connectionString which has been saved in the previous method in startup.cs.
So is it possible to use multiple configuration databases for the identityServer?
One option is to create your own EntityFramework configuration backend for IdentityServer by taking the existing source code and hack the queries made.
Then use a clientID prefix as the "database" identifier/selector, like
A0000-A9999 -> goes to Database A
B0000-B9999 -> goes to Database B
C0000-C9999 -> goes to Database C
or use a clientID like:
AAA:XXX where AAA is the client/DB identifier and XXX is the client within that database.
To select the database/connection string to use. To "fool" IdentityServer to believe that there is only one "database".
Having a structured clientID also makes it easier to debug and reason about the system.
I am authenticating to the Graph API in my Startup.cs:
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = appId,
Authority = "https://login.microsoftonline.com/common/v2.0",
Scope = $"openid email profile offline_access {graphScopes}",
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false // Setting this to true prevents logging in, and is only necessary on a multi-tenant app.
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailedAsync,
AuthorizationCodeReceived = async (context) =>
{
// This block executes once an auth code has been sent and received.
Evar idClient = ConfidentialClientApplicationBuilder.Create(appId)
.WithRedirectUri(redirectUri)
.WithClientSecret(appSecret)
.Build();
var signedInUser = new ClaimsPrincipal(context.AuthenticationTicket.Identity);
var tokenStore = new SessionTokenStore(idClient.UserTokenCache, HttpContext.Current, signedInUser);
string[] scopes = graphScopes.Split(' ');
var result = await idClient.AcquireTokenByAuthorizationCode(scopes, context.Code).ExecuteAsync();
var userDetails = await GraphUtility.GetUserDetailAsync(result.AccessToken);
After retrieving this access token, I store it into a class variable. The reason why I do this is so that I can retrieve it for use in one of my services (called by an API controller) that interfaces with the Graph API.
public GraphAPIServices(IDbContextFactory dbContextFactory) : base(dbContextFactory)
{
_accessToken = GraphUtility.GetGraphAPIAccessToken();
_graphClient = new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken);
}));
}
The problem that I am running into is that after some time, this access token eventually expires. I obviously can't run Startup.cs again so there is no opportunity to retrieve a new access token.
What I would like to know is if it's possible to exchange this expired access token for a new one without the need to request that the user logs in again with their credentials?
Identity Server Client:
//wpf sample
new Client
{
ClientId = "native.code",
ClientName = "Native Client (Code with PKCE)",
RedirectUris = { "http://127.0.0.1/sample-wpf-app" },
//PostLogoutRedirectUris = { "https://notused" },
RequireClientSecret = false,
AllowedGrantTypes = GrantTypes.Code,
AllowAccessTokensViaBrowser = true,
RequirePkce = true,
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess,
"fiver_auth_api"
},
AllowOfflineAccess = true,
//Access token life time is 7200 seconds (2 hour)
AccessTokenLifetime = 7200,
//Identity token life time is 7200 seconds (2 hour)
IdentityTokenLifetime = 7200,
RefreshTokenUsage = TokenUsage.ReUse
}
WPF app:
var options = new OidcClientOptions()
{
//redirect to identity server
Authority = "http://localhost:5000/",
ClientId = "native.code",
Scope = "openid profile offline_access fiver_auth_api",
//redirect back to app if auth success
RedirectUri = "http://127.0.0.1/sample-wpf-app",
ResponseMode = OidcClientOptions.AuthorizeResponseMode.FormPost,
Flow = OidcClientOptions.AuthenticationFlow.AuthorizationCode,
Browser = new WpfEmbeddedBrowser()
};
I am trying to connect the identity server with wpf app but i always get back a 401.
Identity server is running on : http://localhost:5000/
WPF: http://127.0.0.1/sample-wpf-app
I check the token and is the good one. I also enable AllowOfflineAccess = true.
Why do i always get that error?
Edit: Web Api:
var accessToken = token;
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
//on button click call Web api Get movies
//Initialize HTTP Client
client.BaseAddress = new Uri("http://localhost:5001");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
try
{
HttpResponseMessage response = client.GetAsync("/movies/get").Result;
MessageBox.Show(response.Content.ReadAsStringAsync().Result);
}
catch (Exception)
{
MessageBox.Show("Movies not Found");
}
WPF app need to be async in order to wait for the answer from api.