Initiate and store multiple OAuth2 external authentication challenges in a ASP.NET Core MVC application? - oauth-2.0

I can authenticate against two separate OAuth authentication schemes but it seems only one can be active at a time. I'd like to compare data from two separate SaaS applications and therefore I need two separate Bearer tokens. How can I initiate multiple OAuth challenges when the user loads the application and then store the Bearer Tokens for each? (e.g. in the Context.User cookie?)
My Startup.cs is as follows:
public void ConfigureServices(IServiceCollection services)
{
services.AddRouting();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.LoginPath = "/signin";
options.LogoutPath = "/signout";
})
.AddScheme1 (options =>
{
options.ClientId = Configuration["Scheme1:ClientId"];
options.ClientSecret = Configuration["Scheme1:ClientSecret"];
options.Scope.Add("scope1");
options.SaveTokens = true;
})
.AddScheme2(options =>
{
options.ClientId = Configuration["Scheme2:ClientId"];
options.ClientSecret = Configuration["Scheme2:ClientSecret"];
options.Scope.Add("scope1");
options.SaveTokens = true;
});...
}
The AuthenticationController calls the Challenge overloaded method from the Microsoft.AspNetCore.Mvc.Core assembly that takes a single provider/scheme (passing multiple schemes in the overloaded method seems to be ignored).
[HttpGet("~/signin")]
public async Task<IActionResult> SignIn() => View("SignIn", await HttpContext.GetExternalProvidersAsync());
[HttpPost("~/signin")]
public async Task<IActionResult> SignIn([FromForm] string provider)
{
...
return Challenge(new AuthenticationProperties { RedirectUri = "/" }, provider);
}
Presumably, you'd prompt the user to sign-into one external application, redirect back to the home page, and then prompt them to sign-into the second one, and then allow them to start using the application proper.
If this is possible - e.g. using a "multiple" Auth cookie - how then would I fetch the correct Bearer token and User values for the given scheme? Currently you just seem to fetch the token with a generic "access_token" name and unique user values:
string accessToken = await HttpContext.GetTokenAsync("access_token");
string userID = User.FindFirstValue(ClaimTypes.NameIdentifier);
There does seem to be some information here regarding using a SignInManager but I'm unable to determine if this is applicable to this problem.

I would aim to start with a standard architecture where the user authenticates with the one and only app, and gets only one set of tokens, issued by your own Authorization Server.
SaaS DATA - OPTION 1
Does the user need to get involved in these connections or can you use a back end to back end flow here?
Your C# code could connect to the SaaS provider with the client credentials grant, using the client ID and secret that you reference above. Provider tokens would then be cached in memory, then used by the back end code to return provider data to the UI. This is a simple option to code.
SaaS DATA - OPTION 2
If the user needs to get involved, because the data is owned by them, you might offer UI options like this. After each click the user is redirected again, to get a token for that provider.
View provider 1 data
View provider 2 data
Aim to emulate the embedded token pattern, where the provider tokens are available as a secondary credential. How you represent this could vary, eg you might prefer to store provider tokens in an encrypted cookie.
CODING AND SIMPLICITY
I would not mix up provider tokens with the primary OAuth mechanism of signing into the app and getting tokens via the .NET security framework, which typically implements OpenID Connect. Instead I would aim to code the SaaS connections on demand.
I think you will find it easier to code the SaaS connections with a library approach, such as Identity Model. This will also help you to deal with SaaS provider differences more easily.

I assume you use OIDC schemes.
First, you need to add two cookie schemes, one for each OIDC authentication scheme as their sign in scheme and set their callback path to different values to stop them competing:
services.AddAuthentication()
.AddCookie("Cookie1")
.AddCookie("Cookie2")
.AddOpenIdConnect("OidcScheme1", opt =>
{
opt.SignInScheme = "Cookie1";
opt.CallbackPath = "/signin-oidc-scheme1";
opt.SaveTokens = true;
})
.AddOpenIdConnect("OidcScheme2", opt =>
{
opt.SignInScheme = "Cookie2";
opt.CallbackPath = "/signin-oidc-scheme2";
opt.SaveTokens = true;
});
This will instruct the OIDC handler to authenticate the user from corresponding cookie.
Second, you need a controller action to challenge the user against each OIDC scheme:
[HttpGet]
[Route("login")]
[AllowAnonymous]
public IActionResult Login([FromQuery]string scheme,
[FromQuery]string? returnUrl)
{
return Challenge(new AuthenticationProperties
{
RedirectUri = returnUrl ?? "/"
}, scheme);
}
From your web app, you need to send the user to the Login endpoint twice with different scheme values:
GET /login?scheme=OidcScheme1
GET /login?scheme=OidcScheme2
Or chain them together using the returnUrl:
GET /login?scheme=OidcScheme1&returnUrl=%2Flogin%3Fscheme%3DOidcScheme2
Once signed in, there should be two cookies in the browser window, for example:
To authenticate the user and restore both identities from two cookies, you can use authorization policy:
[HttpGet]
[Authorize(AuthenticationSchemes = "OidcScheme1,OidcScheme2")]
public async Task<IActionResult> SomeOperation()
{
// Two identities, one from each cookie
var userIdentities = User.Identities;
...
}
To get access token from each authentication scheme, use the method you discovered (GetTokenAsync) and specify authentication scheme:
var token1 = await HttpContext.GetTokenAsync("OidcScheme1", "access_token");
var token2 = await HttpContext.GetTokenAsync("OidcScheme2", "access_token");
It is possible that the access token is not returned from the token endpoint depends on the response_type you used. If this is the case, try set the OpenIdConnectionOptions.ResponseType to OpenIdConnectResponseType.Code and make sure the scope is correct.

I encountered a similar problem where we had microservices that are/were shared across multiple products with each product having a separate IDP tenant (essentially a different token issuer). Perhaps a similar approach might work for your scenario...
The following link helped me with a solution - see here.
Basically I defined a smart authentication scheme
var builder = services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = "smart";
//...
});
Then in the smart policy scheme definition, I decode the JWT coming in to work out the issuer from the iss claim in the JWT, so that I can forward to the correct location for JWT bearer authentication.
builder.AddPolicyScheme("smart", "smart", options =>
{
options.ForwardDefaultSelector = context =>
{
var jwtEncodedString = context.Request.Headers["Authorization"].FirstOrDefault()?.Substring(7);
if (string.IsNullOrEmpty(jwtEncodedString))
return settings.Tenants.First().Key; // There's no authorization header, so just return any.
var token = new JwtSecurityToken(jwtEncodedString: jwtEncodedString);
var issuer = token.Claims.First(c => c.Type == "iss").Value?.TrimEnd('/');
var tenant = settings.Tenants
.Where(pair => pair.Value.Issuer.TrimEnd('/') == issuer)
.Select(pair => pair.Key).FirstOrDefault();
if (tenant == null)
throw new AuthorizationException($"Failed to locate authorization tenant with issuer '{issuer}'.");
return tenant;
};
});
Note: settings.Tenants is just an array of whitelisted tenants (from appsettings) that I configure as follows:
foreach (var tenant in settings.Tenants)
builder.AddJwtBearer(tenant.Key, options => Configure(options, tenant.Value, defaultJwtBearerEvents));

Related

How client application uses the scopes and resources extracted from access token to restrict the access of API - identityserver4

I can see many links describing how to use identityserver4.
Host application:
Configuring clients with [clientId, secret, APIScopes, APIResources, IdentityResources]
Passing clients details to identityserver4
Client Application:
Passing client id to the endpoint to get access token and refresh token that contains scopes and resources of the defined clients. using that scope and resources we can restrict the access of the API's.
But I am still wondering how the client application will use the API scopes to restrict the access of the Application is there any sample how to utilize the scopes to restrict the application access?
And also approach for maintaining roles in identitserver4
I don't find any links describing how to use the client part after getting access token, please share me any reference that can help me?
In a API (AddJwtBearer), you do two things, authentication and authorization.
In the authorization stage, you check the claims (the scopes is part of the claims) found in the access token.
Can do the authorization check using the role or policy based approach.
A sample policy can look like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy("ViewReports", policy =>
policy.RequireAuthenticatedUser()
.RequireRole("Finance")
.RequireRole("Management")
);
});
then you can decorate your controllers with this attribute:
[Authorize("ViewReports")]
public class SecretController : Controller
{
}
From a consumer (API) point of view, the scopes are just like all the other claims. they are not treated differently.
When using identityserver4, client needs to configure the ClientId and the authority address, this server need to configure the allowed scope corresponding to clientid.
services.AddAuthentication(config=>
{
config.DefaultScheme = "cookie";
config.DefaultChallengeScheme = "oidc";
})
.AddCookie("cookie")
.AddOpenIdConnect("oidc", config=>
{
config.Authority = "url";
config.ClientId = "client_id";
config.ClientSecret = "client_secret";
config.SaveTokens = true;
config.ResponseType = "code";
});
The Identityserver will authorize some functions according to the ClientId.
new Client
{
ClientId="client_id",
ClientSecrets=
{
new Secret("client_secret".ToSha256())
},
AllowedGrantTypes=GrantTypes.Code,
RedirectUris={ "https://localhost:[port]/signin-oidc"},
AllowedScopes={ "apione", "apitwo",
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
//...
},
RequireConsent=false
}
In apione, you still need to configure the audience.
services.AddAuthentication("jwtauth")
.AddJwtBearer("jwtauth",config=>
{
config.Authority = "identityserver url";
config.Audience = "apione";
config.RequireHttpsMetadata = false;
IdentityModelEventSource.ShowPII = true;
});
Every user has their own role, so you can add the claims after the user logs in on the server. In addition, the project api can configure the AddAuthorization as Tore Nestenius says.

How to make secure authentication for .NET Core Web API?

I am developing an app with .NET Core Web API, Entity Framework and React. I've been reading a lot recently about possible authentication techniques for my API and I've discovered that plain JWT is not entirely secure, so at first I decided to use OpenID Connect with IdentityServer 4. I understand the idea behind OAuth 2.0 and OpenID Connect is to hide user credentials during login process and to involve external authentication provider in issuing an access token, but I don't want to rely on such services because not everyone have an account on Facebook etc. I consider this as an optional way to login. I want to give users an ability to sign in with just login and password. So what is the best (secure) way to accomplish this in modern web apps?
Having project 1 as Client App, project 2 as API Resources and project 3 as Authorization Service (IdentityServer4), I consider following scenarios:
A user is able to create an account on Authorization Service which is responsible for issuing a token required to get access to API Resources through Client App. Authorization Service is registered as authorization provider only for my Client App.
Get authorization token from Authorization Service using resource owner password grant - this one is not recommended by the specs but in my case since user must provide credentials to Authorization Service anyway and I will be hosting every project I can't see any problem.
Don't bother with OAuth and implement authorization mechanism using ASP.NET Core Identity + bearer token authentication.
Any ideas or recommendations highly apprecieated.
I use the JwtBearer package, wire it up in your Startup.cs Configure method like
.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration["AppSettings:AuthConfig:SecretKey"])),
ValidateIssuer = true,
ValidIssuer = Configuration["AppSettings:AuthConfig:Issuer"],
ValidateAudience = true,
ValidAudience = Configuration["AppSettings:AuthConfig:Audience"],
ValidateLifetime = true,
}
})
and my login action on my User controller looks like
[HttpPost]
public string Post([FromBody]LoginRequest request)
{
var contact = dbContext.Contacts.Where(c => c.Active && c.Email == request.Email).Select(c => new { c.Id, c.PasswordHash }).SingleOrDefault();
if (contact == null || !Security.PasswordHash.ValidatePassword(request.Password, contact.PasswordHash))
{
return string.Empty;
}
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(appSettings.AuthConfig.SecretKey));
var now = DateTime.UtcNow;
var claims = new Claim[]
{
new Claim(JwtRegisteredClaimNames.Sub, contact.Id.ToString()),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.Now.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64)
};
var jwt = new JwtSecurityToken(
issuer: appSettings.AuthConfig.Issuer,
audience: appSettings.AuthConfig.Audience,
claims: claims,
notBefore: now,
expires: now.AddDays(30),
signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256));
jwt.Payload.Add("roles", dbContext.ContactRoles.Where(cr => cr.ContactId == contact.Id).Select(ur => ur.Role.Name).ToArray());
return new JwtSecurityTokenHandler().WriteToken(jwt);
}
I use a JWT package for Angular on the client, there may be something similar for React.

.NET Core OpenId Connect Server: Sharing same token across multiple applications

I have two apis written in .NET Core and targeting 4.6.1:
myAuthApi (http://localhost:8496): which verifies credentials and issues tokens to clients. It also has an endpoint /api/values/1 (with an Authorize attribute on this action, used to validate tokens)
myPublicApi(http://localhost:8497): which receives tokens from the client on /api/values/1 (with an Authorize attribute on this action, also used to validate tokens). myPublicApi does not have any tokens endpoint and is meant to be a resource server.
I am using AspNet.Security.OpenIdConnect.Server 1.0.0.
Both APIs are Service.Fabric Stateless Services
I can successfully get the token with the following request format to http://localhost:8496/connect/token
client_id=XX&client_secret=XXX&grant_type=password&username=XXX&password=XXX
When validating the token against myAuthApi (http://localhost:8496/api/values/1) it works. However, when using that same token against myPublicApi(http://localhost:8497/api/values/1) it does not.
In both APIs, in the Startup.cs, I have
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Connect to Redis database.
var redis = ConnectionMultiplexer.Connect(ConnectionHelper.GetRedisConnectionString(Configuration));
services.AddDataProtection()
.PersistKeysToRedis(redis, "DataProtection-Keys")
.ProtectKeysWithCertificate(CertificateHandler.GetX509Certificate2(Configuration));
// Add framework services.
services.AddMvc().AddJsonOptions(opts =>
{
// we set the json serializer to follow camelCaseConventions when
// receiving /replying to JSON requests
opts.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
// we add authentication for the oAuth middleware to be registered in the DI container
services.AddAuthentication();
}
In myPublicApi I have:
// 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)
{
// Add a new middleware validating access tokens.
app.UseOAuthValidation(options =>
{
// Automatic authentication must be enabled
// for SignalR to receive the access token.
options.AutomaticAuthenticate = true;
options.Events = new OAuthValidationEvents
{
// Note: for SignalR connections, the default Authorization header does not work,
// because the WebSockets JS API doesn't allow setting custom parameters.
// To work around this limitation, the access token is retrieved from the query string.
OnRetrieveToken = context =>
{
// Note: when the token is missing from the query string,
// context.Token is null and the JWT bearer middleware will
// automatically try to retrieve it from the Authorization header.
context.Token = context.Request.Query["access_token"];
return Task.FromResult(0);
}
};
});
app.UseMvc();
}
In myAuthApi I have:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// Add a new middleware validating access tokens.
app.UseOAuthValidation(options =>
{
// Automatic authentication must be enabled
// for SignalR to receive the access token.
options.AutomaticAuthenticate = true;
options.Events = new OAuthValidationEvents
{
// Note: for SignalR connections, the default Authorization header does not work,
// because the WebSockets JS API doesn't allow setting custom parameters.
// To work around this limitation, the access token is retrieved from the query string.
OnRetrieveToken = context =>
{
// Note: when the token is missing from the query string,
// context.Token is null and the JWT bearer middleware will
// automatically try to retrieve it from the Authorization header.
context.Token = context.Request.Query["access_token"];
return Task.FromResult(0);
}
};
});
// Add a new middleware issuing access tokens.
app.UseOpenIdConnectServer(options =>
{
options.Provider = new AuthenticationProvider();
// Enable the logout, token and userinfo endpoints.
options.LogoutEndpointPath = "/connect/logout";
options.TokenEndpointPath = "/connect/token";
options.UserinfoEndpointPath = "/connect/userinfo";
CertificateHandler.SetupCommonAuthServerOptions(options, Configuration);
});
app.UseMvc();
}
As you can see, my data protection provider is storing keys in Redis, and I am protecting keys with a certificate which I am sharing across the 2 applications.
The resource server does not have any authentication provider configured and does not have UseOpenIdConnectServer in startup. In asp.net Web API 2, the token decryption used to be managed across the apps using shared machine keys.
How can I successfully validate the token issued by myAuthApi across all other apps using oAuthValidation?
EDIT, some logs can be seen here:
https://pastebin.com/ACDz1fam
EDIT2 :
After reading the logs thoroughly, I saw that the unprotection of the token was using the same Data Protection Provider, but different purposes:
"Performing unprotect operation to key {4406cfa7-a588-44ba-b73a-e25817d982d9} with purposes ('C:\SfDevCluster\Data\_App\_Node_4\TestMicroServicesType_App22\PublicApiPkg.Code.1.0.1', 'OpenIdConnectServerHandler', 'AccessTokenFormat', 'ASOS')."
"Performing unprotect operation to key {4406cfa7-a588-44ba-b73a-e25817d982d9} with purposes ('C:\SfDevCluster\Data\_App\_Node_3\TestMicroServicesType_App22\AuthApiPkg.Code.1.0.1', 'OpenIdConnectServerHandler', 'AccessTokenFormat', 'ASOS')."
To fix this, #PinpointTownes suggested to setup the data protection provider like so:
var redis = ConnectionMultiplexer.Connect(ConnectionHelper.GetRedisConnectionString(Configuration));
services.AddDataProtection()
// set the application name to a common value in all apps
// to have the same purpose and share the token across apps
.SetApplicationName("MyTestMicroServices")
.PersistKeysToRedis(redis, "DataProtection-Keys")
.ProtectKeysWithCertificate(CertificateHandler.GetX509Certificate2(Configuration));
Call services.AddDataProtection().SetApplicationName("[your application name]") to ensure your two APIs use the same discriminator (used to derive the crypto keys) and it should work.

Refreshing access tokens in IdentityServer4 clients

I wonder how to refresh a access token in a IdentityServer4 client using the hybrid flow and which is built using ASP.NET Core MVC.
If I have understood the whole concept correctly the client first need to have the "offline_access" scope in order to be able to use refresh tokens which is best practice to enable short lived access tokens and ability to revoke refresh tokens preventing any new access tokens to be issued to the client.
I successfully get a access token and a refresh token, but how should I handle the actual update procedure of the access token in the MVC client?
Can the OpenId Connect (OIDC) middleware handle this automatically? Or should I rather check the expire time of the access token everywhere I call WEB Api's by basically check if the access token have expired or will expire very soon (upcoming 30 seconds) then refresh the access token by calling the token endpoint using the refresh token?
Is it recommended to use the IdentityModel2 library TokenClient extension method RequestRefreshTokenAsync in my Controller action methods for calling the token endpoint?
I have seen code that in the OIDC middleware events request access token and using the response store a claim containing a expire datetime. The problem is that my OIDC in somehow already request a access token automatically so it doesn't feel good to request a new access token directly after recieving the first one.
Example of a Controller action method without access token refresh logic:
public async Task<IActionResult> GetInvoices()
{
var token = await HttpContext.Authentication.GetTokenAsync("access_token");
var client = new HttpClient();
client.SetBearerToken(token);
var response = await client.GetStringAsync("http://localhost:5001/api/getInvoices");
ViewBag.Json = JArray.Parse(response).ToString();
return View();
}
The OIDC middleware will not take care of this for you. It's being executed when it detects a HTTP 401 response, it then redirects the user to IdentityServer login page. After the redirection to your MVC application, it will turn claims into a ClaimsIdentity and pass this on to the Cookies middleware which will materialise that into a session cookie.
Every other request will not involve the OIDC middleware as long as the cookie is still valid.
So you have to take care of this yourself. Another thing you want to consider is that whenever you're going to refresh the access token, you'll have to update the existing one so you don't lose it. If you don't do this, the session cookie will always contain the same token - the original one - and you'll refresh it every time.
A solution I found is to hook that into the Cookies middleware.
Here's the general flow:
On every request, use the Cookies middleware events to inspect the access token
If it's close to its expiration time, request a new one
Replace the new access and refresh tokens in the ClaimsIdentity
Instruct the Cookies middleware to renew the session cookie so it contains the new tokens
What I like with this approach is that in your MVC code, you're pretty much guaranteed to always have a valid access token, unless refereshing the token keeps failing several times in a row.
What I don't like is that it's very tied to MVC - more specifically the Cookies middleware - so it's not really portable.
You can have a look at this GitHub repo I put together. It indeed uses IdentityModel as this takes care of everything and hides most of the complexity of the HTTP calls you'd have to make to IdentityServer.
I created a solution based on a action filter togheter with the OIDC middleware in ASP.NET Core 2.0.
AJAX requests will also go via the action filter hence update the access token/refresh token.
https://gist.github.com/devJ0n/43c6888161169e09fec542d2dc12af09
I found two possible solutions, both are equal but happens at different times in the OIDC middleware. In the events I extract the access token expire time value and store it as a claim which later can be used to check if it's OK to call an Web API with the current access token or if I rather should request a new access token using the refresh token.
I would appreciate if someone could give any input on which of these events are preferable to use.
var oidcOptions = new OpenIdConnectOptions
{
AuthenticationScheme = appSettings.OpenIdConnect.AuthenticationScheme,
SignInScheme = appSettings.OpenIdConnect.SignInScheme,
Authority = appSettings.OpenIdConnect.Authority,
RequireHttpsMetadata = _hostingEnvironment.IsDevelopment() ? false : true,
PostLogoutRedirectUri = appSettings.OpenIdConnect.PostLogoutRedirectUri,
ClientId = appSettings.OpenIdConnect.ClientId,
ClientSecret = appSettings.OpenIdConnect.ClientSecret,
ResponseType = appSettings.OpenIdConnect.ResponseType,
UseTokenLifetime = appSettings.OpenIdConnect.UseTokenLifetime,
SaveTokens = appSettings.OpenIdConnect.SaveTokens,
GetClaimsFromUserInfoEndpoint = appSettings.OpenIdConnect.GetClaimsFromUserInfoEndpoint,
Events = new OpenIdConnectEvents
{
OnTicketReceived = TicketReceived,
OnUserInformationReceived = UserInformationReceived
},
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = appSettings.OpenIdConnect.NameClaimType,
RoleClaimType = appSettings.OpenIdConnect.RoleClaimType
}
};
oidcOptions.Scope.Clear();
foreach (var scope in appSettings.OpenIdConnect.Scopes)
{
oidcOptions.Scope.Add(scope);
}
app.UseOpenIdConnectAuthentication(oidcOptions);
And here is some event examples I'm can choose among:
public async Task TicketReceived(TicketReceivedContext trc)
{
await Task.Run(() =>
{
Debug.WriteLine("TicketReceived");
//Alternatives to get the expires_at value
//var expiresAt1 = trc.Ticket.Properties.GetTokens().SingleOrDefault(t => t.Name == "expires_at").Value;
//var expiresAt2 = trc.Ticket.Properties.GetTokenValue("expires_at");
//var expiresAt3 = trc.Ticket.Properties.Items[".Token.expires_at"];
//Outputs:
//expiresAt1 = "2016-12-19T11:58:24.0006542+00:00"
//expiresAt2 = "2016-12-19T11:58:24.0006542+00:00"
//expiresAt3 = "2016-12-19T11:58:24.0006542+00:00"
//Remove OIDC protocol claims ("iss","aud","exp","iat","auth_time","nonce","acr","amr","azp","nbf","c_hash","sid","idp")
ClaimsPrincipal p = TransformClaims(trc.Ticket.Principal);
//var identity = p.Identity as ClaimsIdentity;
// keep track of access token expiration
//identity.AddClaim(new Claim("expires_at1", expiresAt1.ToString()));
//identity.AddClaim(new Claim("expires_at2", expiresAt2.ToString()));
//identity.AddClaim(new Claim("expires_at3", expiresAt3.ToString()));
//Todo: Check if it's OK to replace principal instead of the ticket, currently I can't make it work when replacing the whole ticket.
//trc.Ticket = new AuthenticationTicket(p, trc.Ticket.Properties, trc.Ticket.AuthenticationScheme);
trc.Principal = p;
});
}
I also have the UserInformationReceived event, I'm not sure if I should use this instead of the TicketReceived event.
public async Task UserInformationReceived(UserInformationReceivedContext uirc)
{
await Task.Run(() =>
{
Debug.WriteLine("UserInformationReceived");
////Alternatives to get the expires_at value
//var expiresAt4 = uirc.Ticket.Properties.GetTokens().SingleOrDefault(t => t.Name == "expires_at").Value;
//var expiresAt5 = uirc.Ticket.Properties.GetTokenValue("expires_at");
//var expiresAt6 = uirc.Ticket.Properties.Items[".Token.expires_at"];
//var expiresIn1 = uirc.ProtocolMessage.ExpiresIn;
//Outputs:
//expiresAt4 = "2016-12-19T11:58:24.0006542+00:00"
//expiresAt5 = "2016-12-19T11:58:24.0006542+00:00"
//expiresAt6 = "2016-12-19T11:58:24.0006542+00:00"
//expiresIn = "60" <-- The 60 seconds test interval for the access token lifetime is configured in the IdentityServer client configuration settings
var identity = uirc.Ticket.Principal.Identity as ClaimsIdentity;
//Keep track of access token expiration
//Add a claim with information about when the access token is expired, it's possible that I instead should use expiresAt4, expiresAt5 or expiresAt6
//instead of manually calculating the expire time.
//This claim will later be checked before calling Web API's and if needed a new access token will be requested via the IdentityModel2 library.
//identity.AddClaim(new Claim("expires_at4", expiresAt4.ToString()));
//identity.AddClaim(new Claim("expires_at5", expiresAt5.ToString()));
//identity.AddClaim(new Claim("expires_at6", expiresAt6.ToString()));
//identity.AddClaim(new Claim("expires_in1", expiresIn1.ToString()));
identity.AddClaim(new Claim("expires_in", DateTime.Now.AddSeconds(Convert.ToDouble(uirc.ProtocolMessage.ExpiresIn)).ToLocalTime().ToString()));
//identity.AddClaim(new Claim("expires_in3", DateTime.Now.AddSeconds(Convert.ToDouble(uirc.ProtocolMessage.ExpiresIn)).ToString()));
//The following is not needed when to OIDC middleware CookieAuthenticationOptions.SaveTokens = true
//identity.AddClaim(new Claim("access_token", uirc.ProtocolMessage.AccessToken));
//identity.Claims.Append(new Claim("refresh_token", uirc.ProtocolMessage.RefreshToken));
//identity.AddClaim(new Claim("id_token", uirc.ProtocolMessage.IdToken));
});
}

OpenID Connect ASP NET MVC AAD

I implemented a sample app using OpenID Connect standard with ASP NET MVC website. My goal was to outsource storing sensitive data to Azure so i used Azure Active Directory. Since it's impossible to add custom properties to users in Azure i store non sensitive user Claims in our private db. I managed to get this claims and "add" them to the cookie like this:
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
RedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = context =>
{
var objectId = context.AuthenticationTicket.Identity.Claims.First(x => x.Type == "http://schemas.microsoft.com/identity/claims/objectidentifier");
var claims = GetUserClaims(objectId.Value);
foreach (var item in claims)
{
context.AuthenticationTicket.Identity.AddClaim(new System.Security.Claims.Claim(item.Key, item.Value));
}
return Task.FromResult(0);
}
}
This way I added required claims to the cookie so those claims persist in my User object until user sign-out which is fine but there is one Claim which can change during the session ( basically user can change it on one page ). The problem is I can't find how to "change" this Claim in the cookie so it will persist. Ideally I would like to somehow force
AuthorizationCodeReceived
function to be called again. Is it possible ? Or is there another way where I can swap the value stored in the cookie ?
So far my only solution is to log-out user when he change this value so it will force him to sign-out again and my callback for AuthorizationCodeReceived will be called again, but it's not a very user-friendly way.
You can call HttpContext.GetOwinContext().Authentication.SignIn() after you add a claim in identity object to persist the new claim in cookie.

Resources