How can I retrieve the OpenID connect token from the cookie(s) produced by Microsoft's OWIN-based middleware?
I am using Microsoft.Owin.Security.Cookies and Microsoft.Owin.Security.OpenIdConnect to protect a website using an 'implicit flow'. At times I think I might be able understand things better or be able to troubleshoot i I could inspect the "raw" token rather than the object model that gets produced from it.
I understand the information is stored via Cookie, but have not found how I can I retrieve the token from the cookie(s). This is a development environment so I should have access to any certificates/secrets that are needed.
I understand that the token should have 3 segments separated by periods: {header}.{claims}.{signature}. If I can find the token I have learned that I can use jwt.io to view the contents. However, none of my cookies have contents matching that format.
This is the middleware configuration I am using:
app.SetDefaultSignInAsAuthenticationType( CookieAuthenticationDefaults.AuthenticationType );
app.UseCookieAuthentication( new CookieAuthenticationOptions() );
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = stsAuthority,
RedirectUri = baseHostingPath,
ResponseType = "id_token",
Scope = string.Join( " ", "openid", "profile", "email" )
} );
If you want to do this just at debug time, I would suggest giving a try to https://github.com/vibronet/OInspector/tree/dev - it helps you to inspect the token in Fiddler.
If you want to do this in code, you can ensure that the raw token is saved in the ClaimsPrincipal by
Adding
TokenValidationParameters = new TokenValidationParameters
{
SaveSigninToken = true
}
to the options initialization
Retrieving the token via something to the effect of
var ci = (System.Security.Claims.ClaimsIdentity)
ClaimsPrincipal.Current.Identity;
string token = ((System.IdentityModel.Tokens.BootstrapContext)
ci.BootstrapContext).Token;
Related
I am trying to add multiple ways to authenticate users in our app. All but one are working flawlessly. I am able to log in with ASP.NET Core Identity, GitHub, Azure AD, and even API auth, but JWT is giving me a bit of a headache as I always get a 401 response when I pass in an bearer token to the authorization header.
This might have something to do with a custom middleware class that works on this header:
app.UseMiddleware<JwtAuthMiddleware>()
.UseAuthentication()
.UseAuthorization()
The middleware class in question:
public class JwtAuthMiddleware
{
private readonly RequestDelegate _next;
public JwtAuthMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context)
{
string authHeader = context.Request.Headers["Authorization"];
if (authHeader != null)
{
string jwtEncodedString = authHeader[7..]; // Fetch token without Bearer
JwtSecurityToken token = new(jwtEncodedString: jwtEncodedString);
ClaimsIdentity identity = new(token.Claims, "jwt");
context.User = new ClaimsPrincipal(identity);
}
return _next(context);
}
}
The identity setup is fairly simple.
services
.AddAuthentication()
.AddCookie(/*config*/)
.AddGitHub(/*config*/)
.AddJwtBearer(/*config*/)
.AddAzureAd(/*config*/);
string[] authSchemes = new string[]
{
IdentityConstants.ApplicationScheme,
CookieAuthenticationDefaults.AuthenticationScheme,
GitHubAuthenticationDefaults.AuthenticationScheme,
JwtBearerDefaults.AuthenticationScheme,
"InHeader"
};
AuthorizationPolicy authnPolicy = new AuthorizationPolicyBuilder(authSchemes)
.RequireAuthenticatedUser()
.Build();
services.AddAuthorization(options =>
{
options.FallbackPolicy = authnPolicy;
});
I also set a filter in AddMvc:
.AddMvc(options =>
{
AuthorizationPolicy policy = new AuthorizationPolicyBuilder(authSchemes)
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
})
JWT works when options.FallbackPolicy is not set. Otherwise, I get a 401 response. Inspecting the DenyAnonymousAuthorizationRequirement in debug mode confirms this as there is no principal set with the right claims filled out. So it looks like context.User is ignored or reset.
Ideally, I would want to get rid of JwtAuthMiddleware altogether, but I still have to figure out how to combine JWT with cookie authentication in this particular setup. Any thoughts?
You don't need to use this custom JwtAuthMiddleware and define the custom ClaimsPrincipal like that. The problem is that you are just extracting the claims from the token. Setting the ClaimsPrincipal like that wouldn't automatically authenticate the user i.e. Context.User.Identity.IsAuthenticated would be false. Hence you get 401.
The token needs to be validated. You are already using AddJwtBearer which you can customize the code like below if you haven't done that.
JWT bearer authentication performs authentication automatically by extracting and validating a JWT token from the Authorization request header. However, you need to set the TokenValidationParameters which tells the handler what to validate.
services
.AddAuthentication()
.AddJwtBearer("Bearer", options =>
{
// you don't have to validate all of the parameters below
// but whatever you need to, but see the documentation for more details
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = config.JwtToken.Issuer, // presuming config is your appsettings
ValidateAudience = true,
ValidAudience = config.JwtToken.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.JwtToken.SigningKey))
};
})
However, there is a caveat. When combining multiple authentication mechanisms, you need to utilise multiple authentication schemes. They play a role in defining the logic to select the correct authentication mechanism. That is because you can call AddJwtBearer or AddCookie multiple times for different scenarios. If the scheme name is not passed, it would try to utilize the default scheme and the authentication will fail, as it would have no way of knowing which mechanism to use JWT or AzureAd as they both use the Bearer token.
You mentioned that JWT works when FallbackPolicy is not set but you get 401 otherwise. That is because your authorization policy requires a user to be authenticated. The user was never authenticated as I mentioned in the beginning. If FallbackPolicy is not set it would work in the context of JWT being passed in the alright, but there is no requirement to check e.g user is authenticated and has a specific claim or role, so it works.
You would need to define an authentication policy scheme and set the ForwardDefaultSelector property of the PolicySchemeOptions. ForwardDefaultSecltor is used to select a default scheme for the current request that authentication handlers should forward all authentication operations to by default.
So basically you need to set the ForwardDefaultSecltor delegate which uses some logic to forward the request to the correct scheme to handle the authentication.
So the above code would change to:
// scheme names below can be any string you like
services
.AddAuthentication("MultiAuth") // virtual scheme that has logic to delegate
.AddGitHub(GitHubAuthenticationDefaults.AuthenticationScheme, /*config*/) // uses GitHub scheme
.AddAzureAd("AzureAd", /*config*/) // uses AzureAd scheme
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, /*config*/) // uses Cookies scheme
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => { // above code for AddJwtBearer }) // uses Bearer scheme
// this is quite important to use the same virtual scheme name below
// which was used in the authentication call.
.AddPolicyScheme("MultiAuth", null, options =>
{
// runs on each request should return the exact scheme name defined above
options.ForwardDefaultSelector = context =>
{
// if the authorization header is present, use the Bearer scheme
string authorization = context.Request.Headers[HeaderNames.Authorization];
if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Bearer "))
{
return "Bearer";
}
// custom logic for GitHub
...
return GitHubAuthenticationDefaults.AuthenticationScheme;
// custom logic for AzureAd
...
return "AzureAd";
// otherwise fallback to Cookies or whichever is the default authentication scheme
return CookieAuthenticationDefaults.AuthenticationScheme;
};
});
That way, you just remove the JwtAuthMiddleware. AddJwtBearer would automatically add the claims in the Claims object when the token is validated, which you can then use to define custom authorization policies to authorize the user.
I hope that helps.
#Shazad Hassan made some good points in his answer. The custom middleware was in fact not necessary in the end because the System.IdentityModel.Tokens.XXXX assemblies were throwing errors that the tokens were invalid. I never really knew the token was invalid because the middleware assumed it was correct (and content-wise, it was), while all this time the context was running for an unauthenticated user, which became apparent when I applied the fallback policy.
So I rewrote the token generation code and now I no longer get this issue, and I can safely remove the custom middleware. So far so good.
While I still have to bone up on authentication schemes and all that, for the moment it doesn't even seem necessary to have multiple calls to AddAuthentication because this works:
services
.AddAuthentication()
.AddCookie(configureCookieAuthOptions)
.AddGitHub(configureGithub)
.AddAzureAd(config)
.AddJwtBearer(configureJwtBearerOptions)
.AddApiKeyInHeader<ApiKeyProvider>("InHeader", options =>
{
options.Realm = "My realm";
options.KeyName = "X-API-KEY";
});
With this, I am able to log in with every method in the list. Looks promising, but might need to be reviewed by someone who knows the ins and outs of authentication in ASP.NET Core.
So: some clients will be sending a cookie, and some will be sending Authorize header with a bearer token.
These are authentication schemes that you set up with AddAuthentication.
After that you need to set up authorization (AddAuthorization) to grant access to users who are authentication with either of these schemes.
(Authentication means checking that you are who you say you are (users), authorization means what you are and aren't allowed to do (roles) -- it's confusing.)
This follows on from a previous post which started as a general issue and is now more specific.
In short, I've been following guidance (such as this from Microsoft, this from Scott Hanselman, and this from Barry Dorrans) to allow me to share the authentication cookie issued by a legacy ASP.NET web app with a new dotnet core app running on the same domain.
I'm confident that I'm using the recommended Microsoft.Owin.Security.Interop library correctly. On that side (the old ASP.NET app), the CookieAuthenticationOptions are configured with AuthenticationType and CookieName both set to the same value - SiteIdentity. This same value is also used in the interop data protector setup:
var appName = "SiteIdentity";
var encryptionSettings = new AuthenticatedEncryptorConfiguration
{
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
};
var interopProvider = DataProtectionProvider.Create(
new DirectoryInfo(keyRingSharePath),
builder =>
{
builder.SetApplicationName(appName);
builder.SetDefaultKeyLifetime(TimeSpan.FromDays(365 * 20));
builder.UseCryptographicAlgorithms(encryptionSettings);
if (!generateNewKey)
{
builder.DisableAutomaticKeyGeneration();
}
});
ShimmedDataProtector = new DataProtectorShim(
interopProvider.CreateProtector(
"Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware",
appName,
"v2"));
I log in using this app, confirm I have a cookie named SiteIdentity then switch to a new dotnet core app running on the same domain.
There, without adding authentication middleware I can confirm that I can unprotect and deserialize the cookie. I do this by setting up data protection in Startup to match the other app:
var appName = "SiteIdentity";
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(keyRingSharePath))
.SetDefaultKeyLifetime(TimeSpan.FromDays(365 * 20))
.DisableAutomaticKeyGeneration()
.UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration()
{
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
})
.SetApplicationName(appName);
Then in my controller I can use a data protector to manually unprotect the cookie:
var appName = "SiteIdentity";
var protector = _dataProtectionProvider.CreateProtector(
"Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware",
appName,
"v2");
var cookieValue = Request.Cookies[appName];
var format = new TicketDataFormat(protector);
var ticket = format.Unprotect(cookieValue);
I can confirm that ticket.Principal does indeed reference a claims principal representing the account which I signed in with on the other app.
However, I've found it impossible to wire up the cookie authentication middleware to properly protect my endpoints using this cookie. This is what I've added to Startup, after the data protection code above:
var protectionProvider = services.BuildServiceProvider().GetService<IDataProtectionProvider>();
var dataProtector = protectionProvider.CreateProtector(
"Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware",
appName,
"v2");
services
.AddAuthentication(appName)
.AddCookie(appName, options =>
{
options.TicketDataFormat = new TicketDataFormat(dataProtector);
options.Cookie.Name = appName;
});
By my understanding this is telling the middleware that I have an authentication scheme named "SiteIdentity" (the advice is that authentication scheme must match the ASP.NET authentication type) which expects a cookie also called "SiteIdentity" which will contain protected data that the supplied data protector can interpret.
But when I add the attribute [Authorize(AuthenticationSchemes = "SiteIdentity")] to my controller I'm kicked away to a login page.
I can't understand what I'm doing wrong. As I've shown, I can confirm that it is indeed possible to use this data protector and ticket format to interpret the authentication cookie, so I guess I must have something wrong in this middleware wiring, but I'm not sure what.
Please ignore. It turns out that my code is actually correct. I had been working on this solution for long enough that the session represented by the cookie value I was using to test had expireed. Will leave this question here in case the code benefits anyone trying to achieve the same.
I am trying to create Azure AD provisioning for our Saas product (using scim2).
I want multiple customers to be able to connect with their Azure AD tenant.
Microsoft has reference code here: https://github.com/AzureAD/SCIMReferenceCode
However, that is setup to only allow one tenant and also to not use the "secret token" that you set up in azure ad. Even tho the comment specifically states the secret token should not be left empty for production.
Here is the important piece of code from the reference project
// Leave the optional Secret Token field blank
// Azure AD includes an OAuth bearer token issued from Azure AD with each request
// The following code validates the Azure AD-issued token
// NOTE: It's not recommended to leave this field blank and rely on a token generated by Azure AD.
// This option is primarily available for testing purposes.
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = this.configuration["Token:TokenIssuer"];
options.Audience = this.configuration["Token:TokenAudience"];
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
// NOTE: You can optionally take action when the OAuth 2.0 bearer token was validated.
return Task.CompletedTask;
},
OnAuthenticationFailed = AuthenticationFailed
};
});
With that code it works assuming Token:TokenIssuer setting is https://sts.windows.net/<tenant_id>/ where tenant_id is the actual tenant id and TokenAudience is 8adf8e6e-67b2-4cf2-a259-e3dc5476c621 (non gallery app).
But it only works if I leave the "Secret token" empty when I set it up in azure ad (non gallery app under Entrprise applications).
I have tried all sorts of things, adding OnChallenge tells me a challenge is sent if I set the "Secret token" but beyond that I am not getting much further.
Any sample code for handling multiple tenants and secret tokens here would be amazing
Update:
Using options.TokenValidationParameters.IssuerValidator I can validate the issuer and thus make that work with multiple tenants. What I really can't get past right now is making a call work when I enter a "Secret token" here: (see picture)
So I figured out that what they want is a JWT token in that field that I generate.
So first I created a method that generates a web token
private string GenerateJSONWebToken()
{
// Create token key
SymmetricSecurityKey securityKey =
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Token:TokenSigningKey"]));
SigningCredentials credentials =
new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
// Set token expiration
DateTime startTime = DateTime.UtcNow;
DateTime expiryTime = startTime.AddMinutes(120);
// Generate the token
JwtSecurityToken token =
new JwtSecurityToken(
configuration["Token:TokenIssuer"],
configuration["Token:TokenAudience"],
null,
notBefore: startTime,
expires: expiryTime,
signingCredentials: credentials);
string result = new JwtSecurityTokenHandler().WriteToken(token);
return result;
}
In my appsettings.json I added
{
"Logging": {
...
},
"Token": {
"TokenAudience": "xxx-xxx-xxx-xxx",
"TokenIssuer": "https://sts.windows.net/yyyy-yyyy-yyyy/",
"TokenSigningKey": "zzz"
}
}
Token Audience I set to 8adf8e6e-67b2-4cf2-a259-e3dc5476c621 as can be read about here https://learn.microsoft.com/en-us/azure/active-directory/app-provisioning/use-scim-to-provision-users-and-groups.
TL;DR The audience for the token will be the application template ID for the application in the gallery, the application template ID for all custom apps is 8adf8e6e-67b2-4cf2-a259-e3dc5476c621
yyyy part of TokenIssuer is the tenant id of the azure ad tenant
zzz from signing key is simply a key of your choosing.
Now finally I generated a token containing the values from appsettings.json.
I then pasted this key into the "Secret token" field in Azure AD.
Finally, how to make this multi tenant (my next steps)
Remove Token:TokenIssuer from appsettings.json
When you call GenerateJSONWebToken send in the client Azure AD tenant ID and use that instead of static value from appsettings.json (Either your client will give you this, or you have it from connecting your app to them)
In startup.cs notice I have already implemented IssuerValidator. Update this to validate not against appsettings.json but your data store.
You can visit Managing user account provisioning for enterprise apps in the Azure portal know more about the setup:
I have an application that is utilizing Azure AD authentication. I also need to access the Microsoft Graph API for user data. Every example I have found that makes requests to the Graph API is utilizing a cached session token, but since I am using JWT obviously I have no need for storing session state. How can I get a JWT with the proper audience using a JWT with my app as the audience?
For example, here is a request to retrieve a token from the Microsoft Graph AspNetCore Sample:
_userTokenCache = new SessionTokenCache(userId, _memoryCache).GetCacheInstance();
var cca = new ConfidentialClientApplication(
_appId,
_redirectUri,
_credential,
_userTokenCache,
null);
var result = await cca.AcquireTokenSilentAsync(_scopes, cca.Users.First());
return result.AccessToken;
Which utilizes the memory cache to pull the token from a Challenge() redirect sign-in with OpenId Connect cookie. However, since I am using JWT, I already have a bearer token, but with the wrong authority. What do I need to do to acquire a new token that I can use to access the Graph API? I still want the tokens to be authorized for my application id, so I would want a new token that allows me to access the API with server-side rest requests.
Edit: Incorrectly tagged as Azure AD Graph, retagged to Microsoft Graph.
Edit Edit: To clarify, each of the samples I've seen so far is using Session cookies as so:
services.AddAuthentication(sharedOptions => {
sharedOptions.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddAzureAd(options => Configuration.Bind("AzureAd", options))
.AddCookie();
However, I am using JWT so I don't have a token cached:
app.UseJwtBearerAuthentication(new JwtBearerOptions {
Authority = $"{instance}{tenant}",
Audience = audience,
SaveToken = true
});
The JWT that I get from requests to login.microsoftonline.com have my application as the audience, whereas the JWT generated by these samples have https://graph.microsoft.com as the audience. So I need to get (I presume at least) a token for this audience using only the token I got from my standard authentication request.
Don't confuse how you manage your token (i.e. token cache) with the tokens themselves. The reason you cache a token is simply so you can request a refreshed token as needed (refresh_token). The refresh token is only provided for certain sceanios (i.e. when using the authorization_code flow and you've requested the offline_access scope).
If you're using a flow without a refresh token (i.e implicit or client_credentials) then you may not need to cache your token. You generally should still cache them since there is an overhead cost to fetching a token from AAD and caching allows you to only retrieve a new token when the existing one expires.
Using DelegateAuthenticationProvider with an existing Token
All that said, it sounds like you've already got a token in hand. Since the entire point of MSAL (which is where ConfidentialClientApplication comes from) it to retrieve and manage tokens for you, I'm not exactly sure why you'd want to do this. I would simply skip MSAL entirely and just use your existing token.
If you're using the Microsoft Graph .NET Client Library you can drop MSAL entirely and simply use your existing token (access_token) via the DelegateAuthenticationProvider:
var graphServiceClient = new GraphServiceClient(
new DelegateAuthenticationProvider((requestMessage) => {
requestMessage.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", token.access_token);
return Task.FromResult(0);
})
);
As for the "proper audience", I'm not sure I understand the context. Your token will need to include scopes for Microsoft Graph but how you define them depends a bit on how you are getting your token.
v1 Endpoint
If you're using the older Azure AD OAUTH endpoint (aka the v1 Endpoint) then you need to configure your Application permissions via the Azure Portal. In order to switch between different APIs (called "Resources") you need to request offline_access and user the refresh_token. Switching involves requesting a refreshed token while passing in a new resource. The resulting token will then work with that resource.
For example, if my default resource is a SharePoint Online instance (https://tenant.sharepoint.com) then I would normally refresh my token with something like this:
private async Task<string> RequestTokenAsync() {
var data = new Dictionary<string, string>();
data.Add("grant_type", "refresh_token");
data.Add("client_id", _clientId);
data.Add("client_secret", _clientSecret);
data.Add("resource", "https://tenant.sharepoint.com");
data.Add("redirect_uri", RedirectUri);
data.Add("refresh_token ", refresh_token);
HttpClient httpClient = new HttpClient();
var response = await httpClient.PostAsync(_tokenUri, new FormUrlEncodedContent(data));
response.EnsureSuccessStatusCode();
var result = await result.Content.ReadAsStringAsync();
}
Now if I want to make a call to Microsoft Graph I will first need to get a token for the https://graph.microsoft.com resource:
private async Task<string> RequestTokenAsync() {
var data = new Dictionary<string, string>();
data.Add("grant_type", "refresh_token");
data.Add("client_id", _clientId);
data.Add("client_secret", _clientSecret);
data.Add("resource", "https://graph.microsoft.com");
data.Add("redirect_uri", RedirectUri);
data.Add("refresh_token ", refresh_token);
HttpClient httpClient = new HttpClient();
var response = await httpClient.PostAsync(_tokenUri, new FormUrlEncodedContent(data));
response.EnsureSuccessStatusCode();
var result = await result.Content.ReadAsStringAsync();
}
Now I have two tokens, one for SharePoint and one for Microsoft Graph. I can switch between resources by simply refreshing the token for the proper resource. I do have to make sure I refresh properly however since if my refresh_token expires before I can replace it, I've lost my credentials entirely.
If this sounds complicated, it is. Generally you need to build some mechanisms to manage which tokens are live, which tokens need to be replaced, etc. This is what that token cache is all about since MSAL/ADAL handle this for you.
v2 Endpoint
The newer v2 Endpoint is far easier to work with. Rather than resources it uses scopes. These scopes include the resource identifier and can be dynamically assigned as needed.
So while in v1 we might assign user.read from Microsoft Graph and user.read from Outlook Rest API, we can now assign both at once in a single token by requesting https://graph.microsoft.com/user.read and https://outlook.office.com/user.read at the same time. This means we get a single token that can be used with either API without getting into the "refresh to switch resource" business from above.
The downside of v2 is that only a limited number of APIs support it at the moment. If you need to work across a number of APIs, you may still be better off using v1 for this reason.
Hope this helps a little.
I am using the following code to successfully get a token from my MVC web app. However I am unsure how to retrieve the claims that I have added. Should they be returned in the same response as my token?
Thanks!
Startup.cs:
app.UseJwtBearerAuthentication(options =>
{
options.AutomaticAuthenticate = true;
options.Audience = "resource_server";
options.Authority = "https://www.example.com/";
options.RequireHttpsMetadata = false;
});
app.UseOpenIdConnectServer(options =>
{
options.ApplicationCanDisplayErrors = true;
options.AllowInsecureHttp = false;
options.Provider = new AuthorizationProvider();
options.TokenEndpointPath = "/connect/token";
});
Adding claims:
identity.AddClaim("custom_claim", "value", "token id_token");
foreach (string role in await userManager.GetRolesAsync(user))
{
identity.AddClaim(ClaimTypes.Role, role, "id_token token");
}
This is my PostAsync result:
{"resource":"resource_server","scope":"openid profile","token_type":"bearer","access_token":"eyJhbGciOiJSU....","expires_in":"3600"}
Should they be returned in the same response as my token?
Since you're specifying both the id_token and token destinations, your claims should be copied in both the access token and the identity token. You should be able to use any JSON parser to extract the claims you're looking for from the access_token/id_token properties.
Two remarks:
In ASOS beta4, you had to explicitly add scope=openid to your token request to get back an identity token, even if you called ticket.SetScopes("openid"), which probably explains why there's no id_token property in the response you shared. This policy was relaxed in the next version.
In ASOS beta4 (for ASP.NET Core RC1), access tokens were serialized using the JWT format. This is no longer true with the beta5 version, that uses an encrypted format by default. Don't try to read them from the client application: instead, use the id_token, which is meant to be consumed by the client app.