We use IdentityServer to handle SSO authentication across our apps.
My application is an Aspnet core 3.0 website that passes the users Token to javascript. The javascript then calls a separate aspnet 2.2 API.
Problem: Logging a user out and back in does not update the ClaimsPrincipal on the API with new claims.
I have confirmed that the Web application has the new claims.
If I login Incognito or clear my cookies the new claim shows up in the API.
I am not sure where the responsibility for getting the claims should be and how to fix it. I assume the claims are part of the encrypted access_token, therefore I assume the Web app is sending a stale access_token to the API. So is the Web App what I need to fix? And what would be the proper fix?
Api Startup Code
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "Bearer";
options.DefaultChallengeScheme = "Bearer";
})
.AddJwtBearer(options =>
{
options.Authority = oidcSettings.Authority;
options.Audience = oidcSettings.ApplicationName;
options.RequireHttpsMetadata = true;
});
Web App Startup Code
services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
options.OnAppendCookie = cookieContext => { cookieContext.CookieOptions.SameSite = SameSiteMode.None; };
options.OnDeleteCookie = cookieContext =>
{
cookieContext.CookieOptions.SameSite = SameSiteMode.None; // this doesn't appear to get called.
};
});
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
}).AddCookie("Cookies", options =>
{
options.SlidingExpiration = false;
options.ExpireTimeSpan = TimeSpan.FromHours(8);
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = oidcSettings.Authority;
options.RequireHttpsMetadata = true;
options.ClientId = oidcSettings.ClientId;
options.ClientSecret = oidcSettings.ClientKey;
options.ResponseType = OpenIdConnectResponseType.Code;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("offline_access");
options.Scope.Add(oidcSettings.ApplicationName);
options.ClaimActions.MapJsonKey("role", "role"); // claims I am looking for are mapped here
options.Events.OnUserInformationReceived = async (context) =>
{
await Task.CompletedTask; // confirmed that after new sign in I can see updated info here.
};
});
TLDR: Javascript from Web app calls Api using access_token. When user logs out and logs back in, the API does not receive updated claims. I am not sure if the issue is the API needs to call out to identity server for user info or the Web App is not signing out properly and needs to send a fresh access_token?
cfg.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/sas")))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
in my hub i tried to get claims like that
Context.User.Claims
but theyre empty
so is there any option to get my JWT claims in my signalr hub?
I had a similar issue and I've solved it by replacing this:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
with this:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
For some reason, I had to explicitly define the DefaultAuthenticateScheme. I still don't know why the DefaultAuthenticateScheme didn't fallback automatically to the DefaultScheme as it should according to the documentation. If I find out the reason, I'll update the answer.
I'm facing a new issue with swashbuckle and login redirection after make a request and my token has been expired.
I really don't know if is a common issue or if this thins can't be done, that's why I make the question.
My request to my api enponit redirects fine but, when I click the Execute button on swagger UI, the request to https://localhost:port/api/v1/Contacts, the response is 302 and then try to redirect me to my login provider with this issue and I cannot redirect to login again.
What I'm missing here????
this is my authentication config:
//Add Auth options
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddOpenIdConnect(options =>
{
options.ClientId = ClientId;
options.Authority = $"{Instance}/{TenantId}";
options.SignedOutRedirectUri = SignedOutRedirectUri;
// Add needed scopes
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("roles");
options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;
options.SaveTokens = true;
options.Events = new OpenIdConnectEvents
{
OnRemoteFailure = context =>
{
context.HandleResponse();
return Task.CompletedTask;
}
};
})
.AddCookie()
.AddAzureAdBearer(options => configuration.Bind("bearer", options));
I currently have an aspnet core 2.1 application where authentication and authorization is handled with Identity, where users, roles and claims are all saved in de database.
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
Is there a way to switch (only) the authentication to oauth2 and then search the user in the database (when found based on email or login) and then tell Identity that this user is logged in? That way I should not change the authorization functionality in the controllers.
I already tested following oauth2 authentication (provider is github) with success.
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "GitHub";
})
.AddCookie()
.AddOAuth("GitHub", options =>
{
options.ClientId = "my client id";
options.ClientSecret = "my client secret";
options.CallbackPath = new PathString("/Home/Index");
options.AuthorizationEndpoint = "https://github.com/login/oauth/authorize";
options.TokenEndpoint = "https://github.com/login/oauth/access_token";
options.UserInformationEndpoint = "https://api.github.com/user";
options.Events = new OAuthEvents
{
OnCreatingTicket = async context =>
{
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
var user = JObject.Parse(await response.Content.ReadAsStringAsync());
context.RunClaimActions(user);
}
};
});
Problem is that code like if (_signInManager.IsSignedIn(User)) won't work anymore as this authentication is via oath2.
Why does the signinmanager getexternallogininfoasync method always returning null?
I am using VS2015 with the default asp.net core (not framework) project for MVC with individual user accounts (this is a requirement). The purpose of using third party login is to allow users to self register. Role based authorization will be handled by asp.net identity using the identity provided from registering through Azure AD.
Correct me if the following interpretation of the signin in manager is incorrect. This method should provide details on the external login and return a claimsprincipal object which contains the claims provided by the user by the identity provider.
I have used the following guide for setting up OpenIdConnectAuthentication in my Startup.cs (class section below)
https://azure.microsoft.com/en-us/documentation/samples/active-directory-dotnet-webapp-openidconnect/
When I launch the external login provider, it directs me to the organization login page and succeeds.
However the variable info which should be populated by the signinmanager method is null
if I put a breakpoint into the callback class the User is populated and the IsAuthenticated variable is true.
I could drive the functionality of allowing the user to register in the app myself, however, this is my first attempt at implementing third party login and I would like to understand what I am doing wrong as this point.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Add Authentication services.
services.AddAuthentication(sharedOptions => {
sharedOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
});
//services.AddDistributedMemoryCache();
//services.AddSession();
services.AddMvc();
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}
// 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();
app.UseApplicationInsightsRequestTelemetry();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseApplicationInsightsExceptionTelemetry();
app.UseStaticFiles();
app.UseIdentity();
// Configure the OWIN pipeline to use cookie auth.
app.UseCookieAuthentication( new CookieAuthenticationOptions());
//Add external authentication middleware below.To configure them please see http://go.microsoft.com/fwlink/?LinkID=532715
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme,
CallbackPath = "/signin-oidc",
ClientId = Configuration["AzureAD:ClientId"],
Authority = String.Format(Configuration["AzureAd:AadInstance"], Configuration["AzureAd:Tenant"]),
ResponseType = OpenIdConnectResponseType.IdToken,
PostLogoutRedirectUri = Configuration["AzureAd:PostLogoutRedirectUri"],
Events = new OpenIdConnectEvents
{
//OnRemoteFailure = OnAuthenticationFailed,
}
});
//app.UseSession();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
External Login
public IActionResult ExternalLogin(string provider, string returnUrl = null)
{
// Request a redirect to the external login provider.
var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl });
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return Challenge(properties, provider);
}
There have been multiple reported issues with this method producing a null value in the past. This does not happen with any of the supported authentication methods that are supplied out of the box. This is a problem at least with using OAuth with azure AD and following the supplied method in the post. However, there is a workaround that still allows for the use of the default project type. Simply replace the method that produces the ExternalLoginInfo variable (info) with a roll your own ExternalLoginInfo class constructed using the User principle.
ExternalLoginInfo info = new ExternalLoginInfo(User,
"Microsoft",
User.Claims.Where(x=>x.Type== "http://schemas.microsoft.com/identity/claims/objectidentifier").FirstOrDefault().Value.ToString(),
"Microsoft" );
ASP.NET MVC 5 (VS2013 final): Facebook login with OWIN fails (loginInfo is null)
MVC 5 Owin Facebook Auth results in Null Reference Exception
http://blogs.msdn.com/b/webdev/archive/2013/10/16/get-more-information-from-social-providers-used-in-the-vs-2013-project-templates.aspx
I had a similar problem using the OpenIdConnect middleware - I finally fixed it by changing ResponseType to OpenIdConnectResponseType.CodeIdToken (it was being set to 'Code').
Here's a link to my Startup.Auth.vb source:
OWIN OpenID provider - GetExternalLoginInfo() returns null
In my case, I needed to explicitly pass null for cookieScheme when adding it on startup, as discussed in this github issue: https://github.com/AzureAD/microsoft-identity-web/issues/133
services.AddAuthentication(idp.LoginProvider).AddMicrosoftIdentityWebApp(
o => {
o.Instance = config.Instance;
o.Domain = config.Domain;
o.ClientId = config.ClientId;
o.TenantId = config.TenantId;
o.CallbackPath = config.CallbackPath;
o.SignInScheme = IdentityConstants.ExternalScheme;
o.SignOutScheme = IdentityConstants.ExternalScheme;
},
openIdConnectScheme: idp.LoginProvider,
cookieScheme: null // YAR
);
Try after removing SignInScheme
In my case I'm using Github's OAuth to log in. I had to add two things in order for the _signinManager.GetExternalLoginInfoAsync() to not return null and for the [Authorize] attribute to work for users who log in using Github.
I had to add this for Authentication:
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultForbidScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme;
// options.DefaultChallengeScheme = "oidc";
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
}).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,
options =>
{
options.Cookie.SameSite = SameSiteMode.None;
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
options.Cookie.IsEssential = true;
});
And then inside the section where I'm actually adding the Github oauth, I had to add the following:
options.SignInScheme = "Identity.External";
So the whole section for Github oauth for me looks like this:
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultForbidScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme;
// options.DefaultChallengeScheme = "oidc";
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
}).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,
options =>
{
options.Cookie.SameSite = SameSiteMode.None;
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
options.Cookie.IsEssential = true;
});
services.AddAuthentication().AddOAuth("GitHub", options =>
{
var key = _env.EnvironmentName == Production ? "GitHubProd" : "GitHub";
options.ClientId = Configuration[$"{key}:ClientId"];
options.ClientSecret = Configuration[$"{key}:ClientSecret"];
options.CallbackPath = new PathString("/github-oauth");
options.AuthorizationEndpoint = "https://github.com/login/oauth/authorize";
options.TokenEndpoint = "https://github.com/login/oauth/access_token";
options.UserInformationEndpoint = "https://api.github.com/user";
options.SaveTokens = true;
options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id");
options.ClaimActions.MapJsonKey(ClaimTypes.Name, "name");
options.ClaimActions.MapJsonKey("urn:github:login", "login");
options.ClaimActions.MapJsonKey("urn:github:url", "html_url");
options.ClaimActions.MapJsonKey("urn:github:avatar", "avatar_url");
options.Scope.Add("repo");
options.Scope.Add("repo_deployment");
options.Scope.Add("repo:status");
options.Scope.Add("write:repo_hook");
options.Scope.Add("read:repo_hook");
options.Scope.Add("notifications");
options.Scope.Add("read:repo_hook");
options.Scope.Add("user");
// NEW LINE HERE
options.SignInScheme = "Identity.External";
// save oauth token to cookie
options.Events = new OAuthEvents
{
OnCreatingTicket = async context =>
{
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
var response = await context.Backchannel.SendAsync(request,
HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
var json = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
context.RunClaimActions(json.RootElement);
}
};
});
why aren't you using the built in microsoftaccountauthentication. There is a tutorial on that here.
https://www.benday.com/2016/05/14/walkthrough-asp-net-core-1-0-mvc-identity-with-microsoft-account-authentication/
This is similar to the google and Facebook authorization classes.