I am using Identity Server 3 with Entity Framework. My ASP.NET MVC app logs in to the SSO/IdentityServer app using below configuration and then that access token is saved in a cookie which is used by javascript to call our API.
Problem is when I login to my ASP.NET MVC app then I go to database and delete that token from the database table, then my API says invalid bearer token as expected, but when I the refresh page in the ASP.NET MVC app, it still shows as logged in and I think it's because of cookie configuration.
How can I ask MVC app to always validate token from server?
AuthConfig.cs of ASP.NET MVC application:
public static class AuthConfig
{
public static void RegisterAuth(IAppBuilder app)
{
ServicePointManager.ServerCertificateValidationCallback =
(sender, certificate, chain, sslPolicyErrors) => true;
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
SlidingExpiration = true,
ExpireTimeSpan = SellutionConstants.Globals.AccessTokenExpirationTimeSpan
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = "sellutionapp",
Authority = SsoConfigHelper.SellutionSts,
ResponseType = "code id_token",
Scope = "openid profile roles all_claims " + SsoConfigHelper.SellutionApiScope,
UseTokenLifetime = false,
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role",
},
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n =>
{
// use the code to get the access and refresh token
var tokenClient = new TokenClient(
SsoConfigHelper.SellutionStsTokenEndpoint,
"sellutionapp",
"secret");
if (String.IsNullOrEmpty(n.RedirectUri))
{
n.RedirectUri = n.Request.Scheme + "://" + n.Request.Host + n.Request.PathBase;
}
var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, n.RedirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
// use the access token to retrieve claims from userinfo
var userInfoClient = new UserInfoClient(
new Uri(SsoConfigHelper.SellutionStsUserInfoEndpoint),
tokenResponse.AccessToken);
var userInfoResponse = await userInfoClient.GetAsync();
// create new identity
var id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
id.AddClaims(userInfoResponse.GetClaimsIdentity().Claims);
id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
//id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
id.AddClaim(new Claim("sid", n.AuthenticationTicket.Identity.FindFirst("sid").Value));
LoginCookieHelper.SetUserData(tokenResponse.AccessToken);
n.AuthenticationTicket = new AuthenticationTicket(
new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, "name", "role"),
n.AuthenticationTicket.Properties);
},
RedirectToIdentityProvider = n =>
{
// This ensures that the address used for sign in and sign out is picked up dynamically from the request
// this allows you to deploy the app (to Azure Web Sites, for example) without having to change settings.
var appBaseUrl = n.Request.Scheme + "://" + n.Request.Host + n.Request.PathBase;
n.ProtocolMessage.RedirectUri = appBaseUrl;
n.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl;
// if signing out, add the id_token_hint
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");
if (idTokenHint != null)
{
n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
}
}
return Task.FromResult(0);
}
}
});
}
}
Identity server configuration:
class Factory
{
public static IdentityServerServiceFactory Configure()
{
var efConfig = new EntityFrameworkServiceOptions
{
ConnectionString = "DefaultConnection",
};
// these two calls just pre-populate the test DB from the in-memory config
ConfigureClients(Clients.Get(), efConfig);
ConfigureScopes(Scopes.Get(), efConfig);
var factory = new IdentityServerServiceFactory();
//var scopeStore = new InMemoryScopeStore(Scopes.Get());
//factory.ScopeStore = new Registration<IScopeStore>(scopeStore);
//var clientStore = new InMemoryClientStore(Clients.Get());
//factory.ClientStore = new Registration<IClientStore>(clientStore);
factory.CorsPolicyService = new Registration<ICorsPolicyService>(new DefaultCorsPolicyService { AllowAll = true });
factory.RegisterOperationalServices(efConfig);
factory.RegisterConfigurationServices(efConfig);
return factory;
}
public static void ConfigureClients(IEnumerable<Client> clients, EntityFrameworkServiceOptions options)
{
using (var db = new ClientConfigurationDbContext(options.ConnectionString, options.Schema))
{
if (!db.Clients.Any())
{
foreach (var c in clients)
{
var e = c.ToEntity();
db.Clients.Add(e);
}
db.SaveChanges();
}
}
}
public static void ConfigureScopes(IEnumerable<Scope> scopes, EntityFrameworkServiceOptions options)
{
using (var db = new ScopeConfigurationDbContext(options.ConnectionString, options.Schema))
{
if (!db.Scopes.Any())
{
foreach (var s in scopes)
{
var e = s.ToEntity();
db.Scopes.Add(e);
}
db.SaveChanges();
}
}
}
}
IdentityServer client configuration
public class Clients
{
public static List<Client> Get()
{
return new List<Client>
{
new Client
{
ClientName = "Resource Owner Flow",
ClientId = "resourceowner",
ClientSecrets = new List<Secret> {new Secret("vkgk8M4pj".Sha256())},
Flow = Flows.ResourceOwner , //Password authentication
PrefixClientClaims = false,
AccessTokenType = AccessTokenType.Jwt,
AllowedScopes = new List<string>
{
Constants.StandardScopes.OpenId,
Constants.StandardScopes.Profile,
Constants.StandardScopes.Email,
Constants.StandardScopes.Roles,
Constants.StandardScopes.Address,
Constants.StandardScopes.AllClaims,
Constants.StandardScopes.OfflineAccess,
SsoConfigHelper.SellutionApiScope
},
RequireConsent = false,
AllowRememberConsent = true,
LogoutSessionRequired = true,
RefreshTokenExpiration = TokenExpiration.Absolute,
RefreshTokenUsage = TokenUsage.OneTimeOnly,
UpdateAccessTokenClaimsOnRefresh = true,
AbsoluteRefreshTokenLifetime =(int)TimeSpan.FromDays(1).TotalSeconds
},
/////////////////////////////////////////////////////////////
// MVC OWIN Implicit Client
/////////////////////////////////////////////////////////////
new Client
{
ClientName = "Sellution Application",
ClientId = "sellutionapp",
Flow = Flows.Hybrid,
AllowAccessTokensViaBrowser = false,
AllowedScopes = new List<string>
{
Constants.StandardScopes.OpenId,
Constants.StandardScopes.Profile,
Constants.StandardScopes.Email,
Constants.StandardScopes.Roles,
Constants.StandardScopes.Address,
Constants.StandardScopes.AllClaims,
SsoConfigHelper.SellutionApiScope
},
ClientSecrets = new List<Secret>
{
new Secret("secret".Sha256())
},
AccessTokenType = AccessTokenType.Reference,
RequireConsent = false,
AllowRememberConsent = true,
LogoutSessionRequired = true,
},
};
}
}
You need to sign the user out of the MVC application as well because according to your code you are using Cookie Authentication named Cookies you also have to do a sign out on that authentication scheme when they log out (not deleteing the token in the store).
AuthenticationManager.SignOut("Cookies"); is what your controller action will require for logout.
Related
I'm trying to schedule events in my O365 user calendars.
I'm using an administrator account, creating a GraphServiceClient on authentication.
There are no errors (apparently) but the event is not created. Neither on the account used nor on the Attendees.
It seems to me that the problem is in the authorization of the GraphServiceClient. I already checked in the Azure portal and the user has "Calendars.ReadWrite" permission.
Any help, please?
try
{
var scopes = new[] { "Calendars.ReadWrite.All" };
// Multi-tenant apps can use "common",
// single-tenant apps must use the tenant ID from the Azure portal
var tenantId = "common";
Configuracoes Dados = new Configuracoes();
// Values from app registration
string clientId = Dados.EMailCV.ToString();
string clientSecret = Dados.PasswordCV.ToString();
// using Azure.Identity;
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
// This is the incoming token to exchange using on-behalf-of flow
var oboToken = "JWT_TOKEN_TO_EXCHANGE";
var cca = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.WithClientSecret(clientSecret)
.Build();
// DelegateAuthenticationProvider is a simple auth provider implementation
// that allows you to define an async function to retrieve a token
// Alternatively, you can create a class that implements IAuthenticationProvider
// for more complex scenarios
var authProvider = new DelegateAuthenticationProvider(async (request) =>
{
// Use Microsoft.Identity.Client to retrieve token
var assertion = new UserAssertion(oboToken);
var result = await cca.AcquireTokenOnBehalfOf(scopes, assertion).ExecuteAsync();
request.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", result.AccessToken);
});
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var #event = new Event
{
Subject = "Let's go for lunch",
Body = new ItemBody
{
ContentType = BodyType.Html,
Content = "Does noon work for you?"
},
Start = new DateTimeTimeZone
{
DateTime = "2022-11-04T12:00:00",
TimeZone = "GMT Standard Time"
},
End = new DateTimeTimeZone
{
DateTime = "2017-04-15T14:00:00",
TimeZone = "GMT Standard Time"
},
// Location = new Location
// {
// displayName = "Harry's Bar"
// },
Attendees = new List<Attendee>()
{
new Attendee
{
EmailAddress = new EmailAddress
{
Address = "p560#esenviseu.net",
Name = "Nuno Barros"
},
Type = AttendeeType.Required
}
},
AllowNewTimeProposals = true,
IsOnlineMeeting = true,
OnlineMeetingProvider = OnlineMeetingProviderType.TeamsForBusiness
};
graphClient.Me.Events
.Request()
.Header("Prefer", "outlook.timezone=\"GMT Standard Time\"")
.AddAsync(#event);
}
catch (Exception ex)
{
lblTexto.Text = "Erros!<br>" + ex.Message;
}
finally
{
lblTexto.Text = "Sem erros!";
}
I'm integrate google calendar api in aps.net mvc application .
I get error "The access token has expired and could not be refreshed. Errors: refresh error, refresh error, refresh error".
the code work correctly when run it on localhost but when I run it on server I have the above error
the code is
public async Task< ActionResult> Index()
{
var userId = User.Identity.GetUserId();
var _User = "Usr" + userId;
string credPath = Server.MapPath($"~/UsersToken/{_User}");
UserCredential UsrCred = null;
FileDataStore fileStore = new FileDataStore(credPath);
TokenResponse tokenResponse = await fileStore.GetAsync<TokenResponse>(userId);
if (!Directory.Exists(credPath))
{
Directory.CreateDirectory(credPath);
}
DirectoryInfo info = new DirectoryInfo(credPath);
DirectorySecurity security = info.GetAccessControl();
security.AddAccessRule(new FileSystemAccessRule("Users", FileSystemRights.FullControl, AccessControlType.Allow));
info.SetAccessControl(security);
if (tokenResponse == null)
{
var result = new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).AuthorizeAsync(CancellationToken.None);
if (result.Result.Credential == null) // <<- it's always true, so it always gets redirected
return Redirect(result.Result.RedirectUri);
else
{
UsrCred = result.Result.Credential;
}
}
else
{
IAuthorizationCodeFlow flow =
new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = _clientId,
ClientSecret = _clientSecret
},
Scopes = Scopes,
DataStore = new FileDataStore(credPath, true)
//DataStore = new FileDataStore(Google.Apis.Auth)
});
UsrCred = new UserCredential(flow, userId, tokenResponse);
}
// Create Google Calendar API service.
var service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = UsrCred,
ApplicationName = ApplicationName,
});
// Define parameters of request.
EventsResource.ListRequest request = service.Events.List("primary");
request.TimeMin = DateTime.Now;
request.ShowDeleted = false;
request.SingleEvents = true;
request.OrderBy = EventsResource.ListRequest.OrderByEnum.StartTime;
Events events = request.Execute();
List<EventModel> Result = new List<EventModel>();
if (events.Items != null && events.Items.Count > 0)
{
foreach (var eventItem in events.Items)
{
Result.Add(
new EventModel()
{
Sumarry = eventItem.Summary,
Description = eventItem.Description,
StartDate = eventItem.Start.DateTime,
EndDate = eventItem.End.DateTime,
GoogleMeetLink = eventItem.HangoutLink
}
);
}
}
else
{
//Console.WriteLine("No upcoming events found.");
}
return View(Result);
}
the redirect_url method is
public async Task<ActionResult> IndexAsyc(string state, string code, string scope)
{
string Lang = ViewBag.Lang ?? "ar";
string RedirectURL = HttpContext.Request.Url.GetLeftPart(UriPartial.Authority) + $"/{Lang}/Meetings/IndexAsyc";
var userId = User.Identity.GetUserId();
var _User = "Usr" + userId;
string credPath = Server.MapPath($"~/UsersToken/{_User}");
UserCredential UsrCred = null;
FileDataStore fileStore = new FileDataStore(credPath);
TokenResponse tokenResponse = await fileStore.GetAsync<TokenResponse>(userId);
// create authorization code flow with clientSecrets
GoogleAuthorizationCodeFlow authorizationCodeFlow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
DataStore = new FileDataStore(credPath),
ClientSecrets = new ClientSecrets()
{
ClientId = _clientId,
ClientSecret = _clientSecret
},
Scopes = Scopes
});
//var result = new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).AuthorizeAsync(CancellationToken.None);
if (tokenResponse == null)
{
// token data does not exist for this user
tokenResponse = await authorizationCodeFlow.ExchangeCodeForTokenAsync(
userId, // user for tracking the userId on our backend system
code,
RedirectURL, // redirect_uri can not be empty. Must be one of the redirects url listed in your project in the api console
CancellationToken.None);
}
TokenResponse tokenResponse2 = await fileStore.GetAsync<TokenResponse>(userId);
if (tokenResponse2 != null)
{
return RedirectToAction("Index");
}
var Creds = new UserCredential(authorizationCodeFlow, userId, tokenResponse2);
if (Creds != null) // result.Credential != null )
{
var service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = Creds,//result.Credential,
ApplicationName = ApplicationName,
});
return RedirectToAction("Index");
}
else
{
return new RedirectResult(RedirectURL);
}
}
how can I set token not expire never ?
I want to get my Azure B2C AD access token from the httpContext or similar, but I am using .NET framework 4.7.2. If I was using .Net core, I would use HttpContext.Authentication.GetTokenAsync() .
Background
I am using OpenIdConnect 4.1.0.
My OpenIdConnectAuthenticationOptions looks like this:
private OpenIdConnectAuthenticationOptions CreateOptionsFromSiteConfig(B2CConfig config)
{
OpenIdConnectAuthenticationOptions options = new OpenIdConnectAuthenticationOptions();
options.MetadataAddress = string.Format(_aadInstance, _tenant, config.Policy);
options.AuthenticationType = [B2CAD-POLICY-name];
options.AuthenticationMode = AuthenticationMode.Passive;
options.RedirectUri = config.AzureReplyUri;
options.PostLogoutRedirectUri = config.LogoutRedirectUri;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "emails"
};
options.SaveTokens = true;
options.RedeemCode = true;
var identityProvider = GetIdentityProvider();
options.Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthenticationFailed = AuthenticationFailed,
RedirectToIdentityProvider = notification =>
{
return Task.FromResult(notification.ProtocolMessage.UiLocales = config.UiLocale ?? string.Empty);
},
SecurityTokenValidated = notification =>
{
notification.AuthenticationTicket.Identity.AddClaim(new Claim("idp", "azureadb2c"));
// transform all claims
ClaimsIdentity identity = notification.AuthenticationTicket.Identity;
notification.AuthenticationTicket.Identity.ApplyClaimsTransformations(new TransformationContext(FederatedAuthenticationConfiguration, identityProvider));
return Task.CompletedTask;
},
};
options.ClientId = config.ClientId;
options.Scope = "openid [api-scope-here]";
options.ResponseType = "id_token token";
return options;
}
There is multiple policies with the same name (i.e. multiple AuthenticationTypes with same name).
So far, I have found several suggestions, where the most promising one suggested:
var result = await owinContext.Authentication.AuthenticateAsync([B2CAD-POLICY-name]));
string token = result.Properties.Dictionary["access_token"];
However, the result is always NULL, eventhough I have verified that the b2cad policy is actually present in the OwinContext.
Any help is much appreciated!
I use IdentityServer3. My startup class is bellow.
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Map("/identity", idsrvApp =>
{
var corsPolicyService = new DefaultCorsPolicyService()
{
AllowAll = true
};
var idServerServiceFactory = new IdentityServerServiceFactory()
.UseInMemoryClients(Clients.Get())
.UseInMemoryScopes(Scopes.Get());
//.UseInMemoryUsers(Users.Get());
idServerServiceFactory.CorsPolicyService = new Registration<IdentityServer3.Core.Services.ICorsPolicyService>(corsPolicyService);
idServerServiceFactory.ViewService = new Registration<IViewService, CustomViewService>();
idServerServiceFactory.UserService = new Registration<IUserService>(resolver => new CustomUserService());
var options = new IdentityServerOptions
{
EnableWelcomePage = false,
Factory = idServerServiceFactory,
SiteName = "Justice Identity Server",
IssuerUri = IdentityConstants.ecabinetIssuerUri,
PublicOrigin = IdentityConstants.ecabinetSTSOrigin,
AuthenticationOptions = new IdentityServer3.Core.Configuration.AuthenticationOptions() {
CookieOptions = {
AllowRememberMe=false,
Prefix="IC"
},
EnablePostSignOutAutoRedirect = true,
},
SigningCertificate = LoadSertificate(),
CspOptions = new CspOptions()
{
Enabled = true,
ScriptSrc = "'unsafe-inline'",
ConnectSrc = "*",
FrameSrc = "*"
},
};
idsrvApp.UseIdentityServer(options);
});
}
X509Certificate2 LoadSertificate()
{
return new X509Certificate2(string.Format(#"{0}\certificates\cert.pfx", AppDomain.CurrentDomain.BaseDirectory), "123", X509KeyStorageFlags.MachineKeySet);
}
}
After sometimes I have got "bad request-request too long" ,when I clear cookie it works. I have seen in console a lot of nonce cookies.
Anyone could help me?
thanks you
This is a known issue.
There is more info there: https://github.com/IdentityServer/IdentityServer3/issues/1124
I have a system where I have an MVC website calling a web api. I have used OAUTH and OPENID Connect for my authentication/authorization. I have setup an identity server in another web api project using Thinktecture's IdentityServer3. In the MVC project I am doing the redirect to the identity server in the OWIN Startup class. This is all pretty standard stuff and is working fine.
I have now been asked to put the web api and the identity server behind Azure API Management. This also, is fine as all I need to do from my MVC project is add my subscription key from API management as a header ("Ocp-Apim-Subscription-Key") to any request to the web api. So far so good.
The problem is I now need to add this header to any requests to the identity server and I can't work out how, other than writing my own middleware. My Startup class looks like this:
public class Startup
{
public void Configuration(IAppBuilder app)
{
DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(IocHelper.GetContainer()));
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = ConfigurationHelper.GetClientId(),
Authority = ConfigurationHelper.GetSecurityTokenServiceUrl(),
RedirectUri = ConfigurationHelper.GetPortalHomePageUrl(),
PostLogoutRedirectUri = ConfigurationHelper.GetPortalHomePageUrl(),
ResponseType = "code id_token",
Scope = "openid profile public_api",
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
},
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n =>
{
// use the code to get the access and refresh token
var tokenClient = new TokenClient(ConfigurationHelper.GetTokenEndpointUrl(), ConfigurationHelper.GetClientId(), ConfigurationHelper.GetClientSecret());
var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, n.RedirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
// use the access token to retrieve claims from userinfo
var userInfoClient = new UserInfoClient(ConfigurationHelper.GetUserInfoUrl());
var userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken);
if (userInfoResponse.IsError)
{
throw new Exception(userInfoResponse.Error);
}
// create new identity
var id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
foreach (var c in userInfoResponse.Claims)
{
id.AddClaim(new Claim(c.Type, c.Value));
}
id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn * 2).ToLocalTime().ToString(CultureInfo.InvariantCulture)));
id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
id.AddClaim(new Claim("sid", n.AuthenticationTicket.Identity.FindFirst("sid").Value));
var claimsIdentity = new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, "name", "role");
//claimsIdentity.IsAuthenticated = true;
n.AuthenticationTicket = new AuthenticationTicket(claimsIdentity, n.AuthenticationTicket.Properties);
},
RedirectToIdentityProvider = n =>
{
// if signing out, add the id_token_hint
if (n.ProtocolMessage.RequestType != OpenIdConnectRequestType.LogoutRequest)
{
return Task.FromResult(0);
}
var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");
if (idTokenHint != null)
{
n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
}
// DOESN'T WORK
n.OwinContext.Request.Headers.Append("Ocp-Apim-Subscription-Key", "MY KEY GOES HERE");
// ALSO DOESN'T WORK
n.Request.Headers.Append("Ocp-Apim-Subscription-Key", "MY KEY GOES HERE");
return Task.FromResult(0);
}
}
});
}
}
Is there a way to hi-jack the request to the identity server and add my own header in there?