I have an application ASP.net 4.X it uses UseOpenIdConnectAuthentication for authentication to an Identity server 4 application.
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = identityServerUrl,
ClientId = clientId,
Scope = "openid profile testapi",
ClientSecret = secret,
RedirectUri = basePath + "/signin-oidc",
PostLogoutRedirectUri = basePath,
ResponseType = "id_token token code",
SignInAsAuthenticationType = "Cookies",
UseTokenLifetime = false,
....................
All of this works fine currently. However I have an issue. If something happens in the Identity server and it does not return all of the claims in the token that i need. I need a way to refuse it without ending in some kind of endless loop of login which i currently have.
I was wondering if there is some place in the middlewear that i can add something like
var needed= claims.FirstOrDefault(c => c.Type == "NeedThisClaim")?.Value;
If its not there then kick it out and refuse the user access. I was thinking of putting it in SecurityTokenValidated but I am not sure that's the best place or not. I am also not sure how to reject the token gracefully.
How do you verify that a token contains the required claims when authenticating an application.
Related
My Current Setup is:
I have an Identity server built using Duenede.IdentityServer package running at port 7025.
I have a WebApi which is Dotnet 6 based and below is its OIDC configuration.
AddOpenIdConnect("oidc", o =>
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
o.SaveTokens = true;
o.GetClaimsFromUserInfoEndpoint = true;
o.RequireHttpsMetadata = false;
o.ResponseType = "code";
o.Authority = "https://localhost:7025/";
o.ClientId = "some clientid";
o.ClientSecret = "some secret";
o.Scope.Clear();
o.Scope.Add("openid");
o.Scope.Add("profile");
o.Scope.Add("dotnetapi");
o.NonceCookie.SameSite = SameSiteMode.Unspecified;
o.CorrelationCookie.SameSite = SameSiteMode.Unspecified;
o.ClaimActions.MapUniqueJsonKey("role", "role");
o.ClaimActions.MapUniqueJsonKey("email", "email");
});
Now when web api request the token from the identityserver (OIDC is the challenge scheme and i have a cookie scheme set as default authentication scheme) it gets both id_token and access_token(verified using await HttpContext.GetTokenAsync("access_token"); await HttpContext.GetTokenAsync("id_token");). I can also find user claims in HttpContext.User.FindFirst("some claim");
But i have noticed that there is an extra call to the identity server from web api for the userinfo. I observed that it may be because of o.GetClaimsFromUserInfoEndpoint = true; when i omitted this line i found that user claims are not set, even though i am still getting both id and access token.
So my understanding is the OIDC client of dotnet is using userinfo endpoint to fetch the user claims. But my question is if i am already receiving the access_token why there is an extra call for the userinfo. Can this extra call be prevented?
is there any way so that i receive id_token at first and access_token is then fetched as it is doing now so that same information is not sent twice?
First, you can set this client config in IdentityServer to always include the user claims in the ID token
AlwaysIncludeUserClaimsInIdToken
When requesting both an id token and access token, should the user
claims always be added to the id token instead of requiring the client
to use the userinfo endpoint. Default is false.
The reason for not including it in the ID-token is that increases the size of the id-token and if you store the tokens in the asp.net session cookie, it also can become pretty big.
I wouldn't worry about the extra request that happens when the user authenticates.
I'm working with an very old VB.net application trying to layer in SSO auth using OWIN and KeyCloak. This is all new to me. The approach I'm taking is to create a C# app to sit in between KeyCloak and my VB app. I've been able to get my C# app to open the login screen of KeyCloak, authenticate and return to the C# app or even the VB app. This seems fine.
However, I need the id_token and username to pass to the VB app. When using Fiddler I can see KeyCloak is generating a post back to my return page with the id_token in tow. However, it is on another thread and gets redirected to the original page but without the id_token. I must be missing something. I've seen code where there are notifications wired and I think they should grab the token and user info, but I don't know how to get the notifications to work. There is no explicit documentation to tell me what to do.
Am I supposed to have a listener or callback method to catch the post from KeyCloak? If so can some one show me how to create one?
Note: I've found some Microsoft code using OWIN and Azure and MVC that bring back user info. However, I point this same code to KeyCloak it authenticates but no user info is returned.
Any help will be greatly appreciated.
-Thanks
In my Startup.cs file I have the following (I've tried many different variations to no avail):
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(
CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Sets the ClientId, authority, RedirectUri as obtained from web.config
ClientId = _clientId,
ClientSecret = _clientSecret,
RequireHttpsMetadata = false,
Authority = _authority,
RedirectUri = _redirectUri,
// PostLogoutRedirectUri is the page that users will be redirected to after sign-out. In this case, it is using the home page
PostLogoutRedirectUri = _redirectUri,
Scope = OpenIdConnectScope.OpenIdProfile,
// ResponseType is set to request the id_token - which contains basic information about the signed-in user
ResponseType = OpenIdConnectResponseType.IdToken,
// ValidateIssuer set to false to allow personal and work accounts from any organization to sign in to your application
// To only allow users from a single organizations, set ValidateIssuer to true and 'tenant' setting in web.config to the tenant name
// To allow users from only a list of specific organizations, set ValidateIssuer to true and use ValidIssuers parameter
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true
},
// OpenIdConnectAuthenticationNotifications configures OWIN to send notification of failed authentications to OnAuthenticationFailed method
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
SecurityTokenReceived = OnSecurityTokenReceived
}
}
);
}
I'm working on a MVC Project for a customer (let's call this "Project A") and we have to use OpenIdConnect for authorization which I basically got it working, I will be redirected to the login site if i'm not authorized and so on. But if i want to go to an Action with [Authorize], it makes a request to the OpenIdConnect-Authority to see if i'm logged in and then redirects me to the RedirectUrl which is specified for this Client.
So if I want to go to /Home/Contact (in my example with [Authorize]) I can't get there, because i will always be redirected to my "RedirectUrl".
Do I have to store the Requested Url by myself and then redirect myself?
It doesn't matter if I set the RedirectUrl or not. I think it's a bit strange, because in another Project B we go against our Identity Server 4 and there it works without any problems.
This is the code in my Startup.Auth.cs:
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
ClientSecret = clientSecret,
Authority = authority,
RedirectUri = redirectUri,
ResponseType = "code",
Scope = "openid profile email",
Configuration = new Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfiguration
{
AuthorizationEndpoint = authority + "as/authorization.oauth2"
},
});
I had to set the AuthorizationEndpoint by myself, because it took always a wrong url. The difference here between Project A and Project B is, that the "ResponseType" is set to "code" in Project A. In Project B it is set to "id_token". But i have to set it to "code" because other Response Types are not accepted.
In Project B i also noticed that the ".AspNet.Cookies" is available on my site.
Which is not the case in Project A. Do i have to create it on my own?
In our application we are using Azure Ad OpenIdConnectAuthentication to sign in which will redirect to "https://login.microsoftonline.com/" when calling our application
I think some reason refresh tokens are not generating in our single page application and forcing the user to sign out after 1 hour because of the access token(which will expire in 1 hour).
I've read a too many blogs but I can't get my answers.Any suggestion much appreciated.
Here is my code :
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = Authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
ClientCredential credential = new ClientCredential(clientId, appKey);
string userObjectID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new CustomTokenCache(userObjectID));
//getting the tokens from below line (Access token with expiry time)
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);
return Task.FromResult(0);
},
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/");
return Task.FromResult(0);
}
}
We usually acquiring the token via the implicit flow instead of authorization code grant flow for the SPA application.
The token will return from the authorization endpoint directly instead of from token endpoint. And we can enable it by modify the app's manifest oauth2AllowImplicitFlow property to true to enable the implicit flow.
To renew the access token when it is expired in the implicit flow, we can perform the a hidden iframe request and add the prompt parameter and set its value to none so that users not required to enter their credential again.
To develop easily, we can use the ADAL library provided by Microsoft for the SPA application. We can renew the token via the AuthenticationContext.prototype._renewToken methoed.
More detail about implicit flow, you may refer the links below:
Authentication Scenarios for Azure AD -(Single Page Application (SPA) section)
Understanding the OAuth2 implicit grant flow in Azure Active Directory (AD)
v2.0 Protocols - SPAs using the implicit flow
If using the Authorization Code grant flow you still can solve this problem by requesting a refresh token. The recent versions of ADAL automatically handles refreshing the access token if it it has expired. The OpenIDConnect request should contain the 'offline_access' scope within it's Scope parameter. OpenIdConnectAuthenticationOptions in your question has a property called Scope which can be set accordingly.
I have an existing MVC5 application I am converting from using AspNetIdentity to utilize ThinkTecture Identity Server 3 v2. The OpenID provider is not the biggest issue I'm having, as it seems to be working great. The security token is validated and I'm handling the SecurityTokenValidated notification in a method in order to get additional user info claims and add system-specific permission claims to the claim set, similar to the code below:
OWIN Middleware
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = "https://localhost:44300/identity",
Caption = "My Application",
ClientId = "implicitclient",
ClientSecret = Convert.ToBase64String(SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes("secret"))),
RedirectUri = "http://localhost:8080/",
ResponseType = "id_token token",
Scope = "openid profile email roles",
SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
UseTokenLifetime = false,
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = ClaimsTransformer.GenerateUserIdentityAsync
}
});
Claims Transformer
public static async Task GenerateUserIdentityAsync(SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
var identityUser = new ClaimsIdentity(
notification.AuthenticationTicket.Identity.Claims,
notification.AuthenticationTicket.Identity.AuthenticationType,
ClaimTypes.Name,
ClaimTypes.Role);
var userInfoClient = new UserInfoClient(new Uri(notification.Options.Authority + "/connect/userinfo"),
notification.ProtocolMessage.AccessToken);
var userInfo = await userInfoClient.GetAsync();
identityUser.AddClaims(userInfo.Claims.Select(t => new Claim(t.Item1, t.Item2)));
var userName = identityUser.FindFirst("preferred_username").Value;
var user = MembershipProxy.GetUser(userName);
var userId = user.PersonID;
identityUser.AddClaim(new Claim(ClaimTypes.Name, userId.ToString(), ClaimValueTypes.Integer));
// Populate additional claims
notification.AuthenticationTicket = new AuthenticationTicket(identityUser, notification.AuthenticationTicket.Properties);
}
The problem is that the ClaimsIdentity assigned to the authentication ticket at the end of the transformer is never populated in the System.Web pipeline. When the request arrives to my controller during OnAuthenticationChallenge, I inspect the User property to find an anonymous WindowsPrincipal with a similar WindowsIdentity instance assigned to the Identity property, as if the web config's system.web/authentication/#mode attribute were set to None (at least I believe that's the behavior for that mode).
What might cause a failure by the middleware to set the principal for the user, or for it to be replaced during System.Web's processing with an anonymous Windows identity? I haven't been able to track this down.
EDIT: This occurs irrespective of whether the SecurityTokenValidated notification is handled and claims augmented.
EDIT 2: The cause appears to be making use of ASP.NET State Service session state server in my web.config with cookies. The configuration entry is:
<sessionState mode="StateServer" stateConnectionString="tcpip=127.0.0.1:42424" timeout="30" cookieless="UseCookies" />
This looks to be related to a reported issue in Microsoft.Owin.Host.SystemWeb #197 where cookies are not persisted from the OWIN request context into the System.Web pipeline. There are an assortment of workarounds suggested but I'd like someone to authoritatively point me to a properly vetted solution for this problem.
If you were to capture a fiddler trace, do you see the any .AspNet.Cookies.
I suspect the cookies are not being written. You can use a distributed cache and do away with cookies.
I think you need to include cookie authentication middleware because .AspNet.Cookies thing is written by that middleware. This is how you can integrate that middleware
app.UseCookieAuthentication(new CookieAuthenticationOptions());
Note: Please make sure it should be on top of openid connect middleware
for more details about CookieAuthenticationOptions please goto this link https://msdn.microsoft.com/en-us/library/microsoft.owin.security.cookies.cookieauthenticationoptions(v=vs.113).aspx.