Microsoft.Owin.Security.OpenIdConnect AuthenticationTicket is null on AuthorizationCodeReceived - asp.net

I'm using OpenIdConnectAuthentication with code flow to implement the OpenIdConnect login. But on the AuthorizationCodeReceived, the property notification.AuthenticationTicket is null value. Any advice?
Here is my startup:
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
string auth0RedirectUri = "http://localhost:44335/";
app.UseCookieAuthentication(new CookieAuthenticationOptions(){});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "OIDC",
ClientId = "qKu-JoUguDjzrvBm*****",
ClientSecret = "w7JPnYYIttT8aDYPrZL9lvQzNaXP0QDqyVMu4AHZYWkUrczG4WJThmo3blHEvfz*******",
Authority = "https://******/authorize",
RedirectUri= auth0RedirectUri,
ResponseType = OpenIdConnectResponseType.Code,
Scope = OpenIdConnectScope.Email+" "+OpenIdConnectScope.OpenIdProfile,
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = false // This is a simplification
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = (notification) =>
{
Debug.WriteLine("*** AuthorizationCodeReceived");
//TODO: get access token from token endpoint later
var authClaim = new ClaimsIdentity("OIDC", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);
authClaim.AddClaim(new System.Security.Claims.Claim("Email","abc#mail.com"));
// notification.AuthenticationTicket is null
notification.AuthenticationTicket = new AuthenticationTicket(authClaim, notification.AuthenticationTicket.Properties);
return Task.FromResult(0);
},
AuthenticationFailed = (context) =>
{
Debug.WriteLine("*** AuthenticationFailed");
return Task.FromResult(0);
},
},
UsePkce = false
});
}

I was recently diving into OpenIDConnect on old ASP.NET framework and had a lot of truble as well.
It will be very difficult to answer you question - becasue I don't know what exactly you want to achieve. Basically as far as I understand the flow AuthorizationCodeReceived gets triggered when user logs-in on authentication server side and gets navigated back with Code query parameter. So at this point AuthenticationTicket should be null because nothing really set it yet.
Now developer has a choice if you want to handle CodeRedemption themselves or leave that to the OpenIDConnectAuthenticationHandler.
I personally did not use first option. I used this step only to transform code into jwe token because my Auth server requires it. But if your choice is to handle it youself then probably you need to do something like in samples available on Katana github project:
AuthorizationCodeReceived = async n =>
{
var _configuration = await n.Options.ConfigurationManager.GetConfigurationAsync(n.OwinContext.Request.CallCancelled);
var requestMessage = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Post, _configuration.TokenEndpoint);
requestMessage.Content = new System.Net.Http.FormUrlEncodedContent(n.TokenEndpointRequest.Parameters);
var responseMessage = await n.Options.Backchannel.SendAsync(requestMessage);
responseMessage.EnsureSuccessStatusCode();
var responseContent = await responseMessage.Content.ReadAsStringAsync();
Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectMessage message = new Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectMessage(responseContent);
n.HandleCodeRedemption(message);
}
https://github.com/aspnet/AspNetKatana/blob/beb224c88712b08ce45f1d14bb8cf0cd9d4a8503/samples/Katana.Sandbox.WebServer/Startup.cs#L157
If you will choose not to do it yourself then you will have to set RedeemCode = true on OpenIdConnectAuthenticationOptions. Then handler will get the token and will set context properly.
There is not much of a documentation anywhere but for me very usefull was Katana project on github. Almost whole flow is implemented in https://github.com/aspnet/AspNetKatana/blob/main/src/Microsoft.Owin.Security.OpenIdConnect/OpenidConnectAuthenticationHandler.cs
You can check what is the flow and what each Notfication is used for.
Unfortunately I cannot help you much more because each flow might be different and only way to say in detail what needs to be done is to reproduce your specific environment.

Related

How do i implement google fit api in .net web app with user sign in?

I simply want to use google fit api to retrieve data of the signed in user(testing with two accounts).
But I don't understand how to get the authorization code/access token from the user.
Another possibly related problem, the consent page that list the scopes used doesn't appear.
I tried to use google.auth library
UserCredential credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets()
{
ClientId = " *** ",
ClientSecret = " *** "
}, new[] { FitnessService.Scope.FitnessActivityRead, FitnessService.Scope.FitnessActivityWrite, FitnessService.Scope.FitnessSleepRead, FitnessService.Scope.FitnessSleepWrite },
"user", CancellationToken.None);
FitnessService fitnessService = new FitnessService(new BaseClientService.Initializer()
{
ApplicationName = "Exrecise App",
HttpClientInitializer = credential
});
var resp = await fitnessService.Users.Sessions.List("me").ExecuteAsync();
But it takes me to this error, maybe I entered wrong inputs in console.cloud
Then, I copied the token from outhplayground and pasted it into httpclient
HttpClient http = new HttpClient();
var resp = await http.SendAsync(new HttpRequestMessage()
{
RequestUri = new UriBuilder("https://www.googleapis.com/fitness/v1/users/me/sessions").Uri,
Headers =
{
{ "Authorization","Bearer ya29.token" }
},
Method = HttpMethod.Get
});
This actually got me the results I wanted, but I want to get it from the user signing in.
Question: Can I not use the regular google authentication to achieve this? Or do I have to use the former method?
Here is me adding google authentication
builder.Services.AddAuthentication().AddGoogle(options =>
{
IConfigurationSection auth = builder.Configuration.GetSection("Authentication:Google");
options.ClientId = auth["ClientId"];
options.ClientSecret = auth["ClientSecret"];
options.CallbackPath = "/Home";
options.AuthorizationEndpoint += "?prompt=consent";
});
Is there something I'm missing?
Looks like the problem is that you did not supply a redirect uri (https://cloud.google.com/dotnet/docs/reference/Google.Apis/latest/Google.Apis.Auth.OAuth2.ICodeReceiver)
in GoogleWebAuthorizationBroker.AuthorizeAsync.
You need to add a redirect uri in the Google App and add it to the codeReceiver field. that's where the user will be redirected after accepting the app.
You can find more about machine authorization and OAuth2.0 authentication with google here

PostLogoutRedirectUri always null in identity Server 4 with SPA (Angular 7 OIDC client)

Using the facebook login authentication in angular app with identity server 4. On logout method PostLogoutRedirectUri , ClientName, LogoutId is always null.
private async Task<LoggedOutViewModel> BuildLoggedOutViewModelAsync(string logoutId)
{
// get context information (client name, post logout redirect URI and iframe for federated signout)
var logout = await _interaction.GetLogoutContextAsync(logoutId);
var vm = new LoggedOutViewModel
{
AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut,
PostLogoutRedirectUri = logout?.PostLogoutRedirectUri,
ClientName = string.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout?.ClientName,
SignOutIframeUrl = logout?.SignOutIFrameUrl,
LogoutId = logoutId
};
if (User?.Identity.IsAuthenticated == true)
{
var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value;
if (idp != null && idp != IdentityServer4.IdentityServerConstants.LocalIdentityProvider)
{
var providerSupportsSignout = await HttpContext.GetSchemeSupportsSignOutAsync(idp);
if (providerSupportsSignout)
{
if (vm.LogoutId == null)
{
// if there's no current logout context, we need to create one
// this captures necessary info from the current logged in user
// before we signout and redirect away to the external IdP for signout
vm.LogoutId = await _interaction.CreateLogoutContextAsync();
}
vm.ExternalAuthenticationScheme = idp;
}
}
}
return vm;
}
Angular oidc clident code
logout(): Promise<any> {
return this._userManager.signoutRedirect();
}
Client setup
public IEnumerable<Client> GetClients()
{
var client = new List<Client>
{
new Client
{
ClientId = ConstantValue.ClientId,
ClientName = ConstantValue.ClientName,
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RequireConsent = false,
RedirectUris = { string.Format("{0}/{1}", Configuration["IdentityServerUrls:ClientUrl"], "assets/oidc-login-redirect.html"), string.Format("{0}/{1}", Configuration["IdentityServerUrls:ClientUrl"], "assets/silent-redirect.html") },
PostLogoutRedirectUris = { string.Format("{0}?{1}", Configuration["IdentityServerUrls:ClientUrl"] , "postLogout=true") },
AllowedCorsOrigins = { Configuration["IdentityServerUrls: ClientUrl"] },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
ConstantValue.ClientDashApi
},
IdentityTokenLifetime=120,
AccessTokenLifetime=120
},
};
return client;
}
logoutId is always null. I am successfully able to login to facebook return to the callback method. But redirect uri is always null.
Reference
IdentityServer4 PostLogoutRedirectUri null
This may not be your issue, but it was my issue when I got the same error as you so I am posting my own experience here.
I was following along in a Pluralsight video that was constructing an Angular app using IdentityServer4 as the STS Server, and it directed me to set the post_logout_redirect_uri in the configuration for my UserManager in the AuthService I was constructing, like so:
var config = {
authority: 'http://localhost:4242/',
client_id: 'spa-client',
redirect_uri: `${Constants.clientRoot}assets/oidc-login-redirect.html`,
scope: 'openid projects-api profile',
response_type: 'id_token token',
post_logout_redirect_uri: `${Constants.clientRoot}`,
userStore: new WebStorageStateStore({ store: window.localStorage })
}
this._userManager = new UserManager(config);
An old issue at the github repo https://github.com/IdentityServer/IdentityServer4/issues/396 discussed the fact that this is set automatically now and doesn't need to be set explicitly (see the end of the thread). Once I removed that from the configuration I no longer had the issue where logoutId was null in the AccountController's Logout method:
/// <summary>
/// Show logout page
/// </summary>
[HttpGet]
public async Task<IActionResult> Logout(string logoutId)
So this was the correct setup for the config for me:
var config = {
authority: 'http://localhost:4242/',
client_id: 'spa-client',
redirect_uri: `${Constants.clientRoot}assets/oidc-login-redirect.html`,
scope: 'openid projects-api profile',
response_type: 'id_token token',
userStore: new WebStorageStateStore({ store: window.localStorage })
}
this._userManager = new UserManager(config);
I had a similar issue and for a few hours I was lost. In my case the value/url I had in angular for post_logout_redirect_uri (in the UserManagerSettings) was different than the value/url I had in my IdentityServer4 in the field PostLogoutRedirectUris of the Client configuration. I messed up the routes. It's a silly mistake but sometimes you miss the simple things.

New JWT token has same expiry as old token

I've got JWT token generation working in my ASP.NET Core 2.0 Web API, but I'm running into an issue where subsequent new access tokens have the same expiry as previously generated ones.
For instance, I post login credentials, and return an access token. The access token works as expected on [Authorize] API endpoints. For testing purposed, I set the token to expire after 1 minute. After 1 minute, the token expires and the authenticated endpoints return a 401, as expected.
I'm handling the 401's in my client side application. The login form appears, and the user logs in again. A new token is generated and returned. The only issue is, this new token has the exact same 'ValidTo" DateTime as the initially generated token. Causing any calls after using this new token to return 401 because the token is already expired. I've confirmed that two different tokens are being checked, so it's not an issue with me passing the wrong token
First token failure (expected, as token expired):
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler: Information: Failed to validate the token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZGFtQHBytNDRiYS1...Do1NzM5NS8ifQ.t8DjvlGV7GZ3xucwu-1hlJRXA5owPdP9t7kfYiiJHyQ.
Microsoft.IdentityModel.Tokens.SecurityTokenExpiredException: IDX10223: Lifetime validation failed. The token is expired.
ValidTo: '11/08/2017 19:23:09'
Current time: '11/08/2017 19:23:13'.
Second token failure (not expected, ValidTo same as previous token)
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler: Information: Failed to validate the token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZGFtQ...dDo1NzM5NS8ifQ.2TMPJvYnQl1Jw78M2nj40uD3qejBEciXfKC845saGNI.
Microsoft.IdentityModel.Tokens.SecurityTokenExpiredException: IDX10223: Lifetime validation failed. The token is expired.
ValidTo: '11/08/2017 19:23:09'
Current time: '11/08/2017 19:23:34'.
JWT Configuration in Startup.cs
services.Configure<JwtIssuerOptions>(options => {
options.Issuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
options.Audience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)];
options.SigningCredentials = new SigningCredentials(SigningKey, SecurityAlgorithms.HmacSha256);
options.ValidFor = TimeSpan.FromMinutes(1);
});
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)],
ValidateAudience = true,
ValidAudience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)],
ValidateIssuerSigningKey = true,
IssuerSigningKey = SigningKey,
RequireExpirationTime = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
};
});
Login action where Token is created:
[HttpPost]
public async Task<IActionResult> Login([FromBody]CredentialsViewModel credentials)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var identity = await GetClaimsIdentity(credentials.UserName, credentials.Password);
if (identity == null)
{
return BadRequest(Errors.AddErrorToModelState("login_failure", "Invalid username or password.", ModelState));
}
// Serialize and return the response
var response = new
{
id = identity.Claims.Single(c => c.Type == "id").Value,
auth_token = await _jwtFactory.GenerateEncodedToken(credentials.UserName, identity),
expires_in = (int)_jwtOptions.ValidFor.TotalSeconds
};
var json = JsonConvert.SerializeObject(response, _serializerSettings);
return new OkObjectResult(json);
}
JwtFactory method where token is being generated:
private readonly JwtIssuerOptions _jwtOptions;
public JwtFactory(IOptions<JwtIssuerOptions> jwtOptions)
{
_jwtOptions = jwtOptions.Value;
ThrowIfInvalidOptions(_jwtOptions);
}
public async Task<string> GenerateEncodedToken(string userName, ClaimsIdentity identity)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, userName),
new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()),
new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(), ClaimValueTypes.Integer64),
identity.FindFirst("rol"),
identity.FindFirst("id")
};
// Create the JWT security token and encode it.
var jwt = new JwtSecurityToken(
issuer: _jwtOptions.Issuer,
audience: _jwtOptions.Audience,
claims: claims,
notBefore: _jwtOptions.NotBefore,
expires: _jwtOptions.Expiration,
signingCredentials: _jwtOptions.SigningCredentials);
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
return encodedJwt;
}
The issue was in my jwtFactory, I was dependency injecting IOptions. Since this is defined in the startup, and has several properties that are automatically filled when the object is created (such as IssuedAt, which gets DateTime.NowUtc), IOptions was only returning the configuration of the first time it was loaded.
I was able to solve this by injecting IOptionsSnapshot, which grabs a new version of JwtIssuerOptions, which would have an updated IssuedAt property.
private readonly JwtIssuerOptions _jwtOptions;
public JwtFactory(IOptionsSnapshot<JwtIssuerOptions> jwtOptions)
{
_jwtOptions = jwtOptions.Value;
ThrowIfInvalidOptions(_jwtOptions);
}
Just a suggestion, the _jwtOptions.Expiration should be a timespan, i.e. how long the token should be valid for, so let's say it's 20 and in minutes, in that case you should have expires: DateTime.UtcNow.AddMinutes(_jwtOptions.Expiration) or something similar. And may be even rename it to reflect that.

Multiple Authentication Middlewares ASP.NET Core

I am relatively new to the concept of middlewares. I am aware that a middleware calls the next middleware when it completes.
I am trying to authenticate a request using either Google or my Identity Server. The user can login on my mobile app with google or a local account. However, I can't figure out how to use both authentication middlewares. If I pass the id_token for google, it passes on the first middleware (UseJwtBearerAuthentication) but fails on the second one (UseIdentityServerAuthentication). How can I make it so that it doesn't throw error when it actually passes on at least 1 authentication middleware? For example, if it passes on the first middleware, the second middleware is ignored?
app.UseJwtBearerAuthentication(new JwtBearerOptions()
{
Authority = "https://accounts.google.com",
Audience = "secret.apps.googleusercontent.com",
TokenValidationParameters = new TokenValidationParameters()
{
ValidateAudience = true,
ValidIssuer = "accounts.google.com"
},
RequireHttpsMetadata = false
});
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "http://localhost:1000/",
RequireHttpsMetadata = false,
ScopeName = "MyApp.Api"
});
Normally, when an authentication middleware is failed(i don't mean throwing exception), this doesn't affect another successful authentication middleware. Probably your second middleware throws an exception(not a validation failure). First check error message and try to resolve it. If you can't, use AuthenticationFailed event to handle error. In this case your code should be something like below:
app.UseJwtBearerAuthentication(new JwtBearerOptions()
{
// ...
Events = new JwtBearerEvents()
{
OnAuthenticationFailed = async (context) =>
{
if (context.Exception is your exception)
{
context.SkipToNextMiddleware();
}
}
}
});
However, for your scenerio i wouldn't choose your way. I would use only identity server endpoint. For signing with google you can configure identity server like below:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme,
AutomaticAuthenticate = false,
AutomaticChallenge = false
});
app.UseGoogleAuthentication(new GoogleOptions
{
AuthenticationScheme = "Google",
SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme,
ClientId = "",
ClientSecret = ""
});
app.UseIdentityServer();
Edit
It seems AuthenticationFailed event couldn't be used for IdentityServer4.AccessTokenValidation. I am not sure but if you will use identity server for only jwt token, you can use UseJwtBearerAuthentication for validation.

OpenIDConnect AspNetCore Logout using id_token

Main issue is that I could not find a proper way to logout from identityServer4.
Detailed explanation:
Client side Web application startup.cs contains the following code
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies",
AutomaticAuthenticate = true
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
AuthenticationScheme = "oidc",
SignInScheme = "Cookies",
Authority = "http://localhost:1941/",//local identityServer4
ClientId = "testsoft",
ClientSecret = "secret",
ResponseType = "code id_token token",
GetClaimsFromUserInfoEndpoint = true,
RequireHttpsMetadata = false,
Scope = { "openid", "profile", "email" },
TokenValidationParameters = new TokenValidationParameters()
{
NameClaimType = "name",
RoleClaimType = "role"
},
AutomaticAuthenticate = false,
AutomaticChallenge = true
});
IdentityServer4 running locally has the client added as below
new Client
{
ClientId = "testsoft",
ClientName = "testsoft",
ClientSecrets = new List<Secret>
{
new Secret("secret".Sha256())
},
ClientUri = "http://localhost:55383/",//clientside web application url
AllowedGrantTypes = GrantTypes.Hybrid,
AllowAccessTokensViaBrowser = true,
RedirectUris = new List<string>
{
"http://localhost:55383/signin-oidc"
},
RequireConsent = false,
AllowedScopes = new List<string>
{
StandardScopes.OpenId.Name,
StandardScopes.Profile.Name,
StandardScopes.Email.Name,
StandardScopes.Roles.Name,
StandardScopes.OfflineAccess.Name,
"api1", "api2",
},
},
I was able to login and display the claims on a Controller-View in MVC like this
[Authorize]
public IActionResult About()
{
return View((User as ClaimsPrincipal).Claims);
}
And the view displayed was like this. Note that there is no id_token
And I was able to logout using cookie as given below
public async Task<IActionResult> LogOut()
{
await HttpContext.Authentication.SignOutAsync("Cookies");
return Redirect("~/");
}
But the problem is I cannot find a way to logout from IdentityServer. The closer I came was to use
/connect/endsession?id_token_hint=...&post_logout_redirect_uri=https://myapp.com
But I could not find a way to get raw id_token in code. In the About() method given above I am only getting the claims (which I think is the decrypted contents of id_token) and in those claims list there is no id_token to be seen. But somehow managed to get the id_token from fiddler at this url http://localhost:55383/signin-oidc and then the logout at identityServer triggered(with the help of the url given above).
I have the following questions:
How to get id_token in code? (instead of manual copy from fiddler)
Is there a better way to logout? Or is there an AspnetCore/Oidc framework method to logout (which in turn call the correct server api with correct parameters) ?
I was able to logout and login several times but the id_token was seen the same on fiddler. eg: Bob user, Alice user both had the same id_token. Cookie was cleared and each time different user was displayed on the view still the id_token was same. shouldn't the id_token be different for each login/user?
Signout url worked even when I gave a random string as id_token. Does this mean that IdentityServer4 logout functionality do not work based on id_token?
For logging out, did you try-
HttpContext.Authentication.SignOutAsync("oidc");
in your client's Logout Action?

Resources