MSAL for OpenIdConnect? - asp.net

we have 3 web apps which use OpenIdConnect with the OWIN app builder for user sign in. The packages used in the Startup are:
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.OpenIdConnect;
This is the Configuration method, some code skipped for brevity:
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = commonAuthority,
PostLogoutRedirectUri = postLogoutRedirectUri,
RedirectUri = postLogoutRedirectUri,
TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = false,
RoleClaimType = "roles",
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = AuthorizationCodeReceived,
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error/ShowError?signIn=true&errorMessage=" + context.Exception.Message);
return Task.FromResult(0);
}
}
});
}
I don't see any mention of Microsoft.IdentityModel.Clients.ActiveDirectory (ADAL) anywhere in the Startup code. Correct me if I am wrong but I think there is no action to be taken as part of ADAL to MSAL for OpenIdConnect authentication components.

Related

Getting issue when I tried with integrating Microsoft Graph SDK with asp.net 4.8 web application

I am facing an issue with Microsoft GRAPH sdk.
IDX21323: RequireNonce is 'True'. OpenIdConnectProtocolValidationContext.Nonce was null, OpenIdConnectProtocol.ValidatedIdToken.Payload.Nonce was not null. The nonce cannot be validated. If you don't need to check the nonce, set OpenIdConnectProtocolValidator.RequireNonce to 'false'. Note if a 'nonce' is found it will be evaluated.
public void ConfigureAuth(IAppBuilder app)
{ app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = appId,
Authority = "https://login.microsoftonline.com/common/v2.0",
Scope = $"openid email profile offline_access {graphScopes}",
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
TokenValidationParameters = new TokenValidationParameters
{
// For demo purposes only, see below
ValidateIssuer = false
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailedAsync,
AuthorizationCodeReceived = OnAuthorizationCodeReceivedAsync
}
}
);
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
}
private static Task OnAuthenticationFailedAsync(AuthenticationFailedNotification<OpenIdConnectMessage,
OpenIdConnectAuthenticationOptions> notification)
{
notification.HandleResponse();
string redirect = $"/ErrPage/Errormsg?message={notification.Exception.Message}";
if (notification.ProtocolMessage != null && !string.IsNullOrEmpty(notification.ProtocolMessage.ErrorDescription))
{
redirect += $"&debug={notification.ProtocolMessage.ErrorDescription}";
}
notification.Response.Redirect(redirect);
return Task.FromResult(0);
}

OAuth OIDC .net core error IDX21307: The 'c_hash' claim was not found in the id_token, but a 'code' was in the OpenIdConnectMessage

Hi guys I am trying to add oAuth 2.0 oidc to .net Webforms app running .net 48 and am getting this error
'IDX21307: The 'c_hash' claim was not found in the id_token, but a 'code' was in the OpenIdConnectMessage'
if I don't use MessageReceived notification then it goes to 404 resource not found after going through AuthorizationCodeReceived I used MessageRecieved notification after going through comments on this answer
not sure what to do or how to debug here is the startup.cs
using IdentityModel.Client;
using Microsoft.AspNet.Identity;
using Microsoft.IdentityModel.Logging;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.Notifications;
using Microsoft.Owin.Security.OpenIdConnect;
using Owin;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Net.Http;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web;
[assembly: OwinStartup(typeof(http_SpectraWeb.Startup))]
namespace http_SpectraWeb
{
public class Startup
{
// These values are stored in Web.config. Make sure you update them!
private readonly string _clientId = ConfigurationManager.AppSettings["okta:ClientId"];
private readonly string _redirectUri = ConfigurationManager.AppSettings["okta:RedirectUri"];
private readonly string _authority = ConfigurationManager.AppSettings["okta:OrgUri"];
private readonly string _clientSecret = ConfigurationManager.AppSettings["okta:ClientSecret"];
private readonly string _groupPrefix = ConfigurationManager.AppSettings["okta:GroupPrefix"];
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
List<Claim> claims = new List<Claim>();
private async Task ProcessMessageReceivedNotification(MessageReceivedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> args)
{
if (!string.IsNullOrWhiteSpace(args.ProtocolMessage.Code))
{
// Exchange code for access and ID tokens
TokenClient tokenClient = new TokenClient(new HttpClient() { BaseAddress = new Uri($"{_authority}/v1/token") }, new TokenClientOptions { ClientId = _clientId, ClientSecret = _clientSecret });
var tokenResponse = await tokenClient.RequestAuthorizationCodeTokenAsync(args.ProtocolMessage.Code, _redirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
var client = new HttpClient();
var userInfoResponse = await client.GetUserInfoAsync(new UserInfoRequest
{
Address = $"{_authority}/v1/userinfo",
Token = tokenResponse.AccessToken
});
args.ProtocolMessage.IdToken = tokenResponse.IdentityToken;
claims.AddRange(userInfoResponse.Claims);
claims.AddRange(userInfoResponse.Claims);
claims.Add(new Claim("id_token", tokenResponse.IdentityToken));
claims.Add(new Claim("access_token", tokenResponse.AccessToken));
if (!string.IsNullOrEmpty(tokenResponse.RefreshToken))
{
claims.Add(new Claim("refresh_token", tokenResponse.RefreshToken));
}
}
}
public void ConfigureAuth(IAppBuilder app)
{
IdentityModelEventSource.ShowPII = true;
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = _clientId,
ClientSecret = _clientSecret,
Authority = _authority,
RedirectUri = _redirectUri,
ResponseType = OpenIdConnectResponseType.Code,
Scope = OpenIdConnectScope.OpenIdProfile,
UsePkce = false,
TokenValidationParameters = new TokenValidationParameters { NameClaimType = "name" },
Notifications = new OpenIdConnectAuthenticationNotifications
{
MessageReceived = ProcessMessageReceivedNotification,
RedirectToIdentityProvider = n =>
{
return Task.FromResult(0);
},
AuthorizationCodeReceived = async n =>
{
if (n.AuthenticationTicket != null)
{
n.AuthenticationTicket.Identity.AddClaims(claims);
}
},
},
});
}
}
}
any help would be great
Thanks

ASP.Net MVC Client (.net framework 4.6.2) does not get redirected to after logout

I have a Identity Server 4 implementation in .Net core 3. And I also created 3 clients: Angular, .Net Core MVC (.Net Core 3.0) and .Net framework MVC (.Net framework 4.6.2).
The Angular and .Net Core MVC clients work without any problems but I have a problem with the .Net framework MVC client. It will not redirect back to the client from Identity Server.
.Net Framework MVC startup
private void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions {AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = OpenIdConnectAuthenticationDefaults.AuthenticationType,
SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
Authority = "https://localhost:5001/",
RequireHttpsMetadata = false,
ResponseType = "id_token",
RedirectUri = "https://localhost:44333/signin-oidc",
PostLogoutRedirectUri = "https://localhost:44333/signout-callback-oidc",
ClientId = "mvc-framework",
SaveTokens = true
});
}
Logout code:
[Authorize]
public ActionResult SignOut()
{
HttpContext.GetOwinContext().Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType, OpenIdConnectAuthenticationDefaults.AuthenticationType);
return RedirectToAction("Index", "Home");
}
Identity Server Setup:
internal static IServiceCollection AddConfiguredIdentityServer4InMemory(this IServiceCollection services, IConfiguration configuration, IWebHostEnvironment webHostingEnvironment)
{
var builder = services.AddIdentityServer()
.AddInMemoryIdentityResources(InMemoryData.GetIdentityResources())
.AddInMemoryApiResources(InMemoryData.GetApiResources())
.AddInMemoryClients(InMemoryData.GetClients())
.AddTestUsers(InMemoryData.GetUsers());
if (webHostingEnvironment.IsDevelopment())
builder.AddDeveloperSigningCredential();
else
throw new Exception("need to configure key material"); //ToDo: work with certificate in key vault.
return services;
}
Client configuration:
internal static IEnumerable<Client> GetClients()
{
return new[]
{
// OpenID Connect implicit flow MVC .Net Framework client
new Client
{
ClientId = "mvc-framework",
ClientName = "MVC .Net Framework Client",
AllowedGrantTypes = GrantTypes.Implicit,
RequireConsent = false,
// where to redirect to after login
RedirectUris = { "https://localhost:44333/signin-oidc" },
// where to redirect to after logout
PostLogoutRedirectUris = { "https://localhost:44333/signout-callback-oidc" },
// scopes
AllowedScopes = new List<string> {IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile}
},
// OpenID Connect implicit flow MVC .Net Core client
new Client
{
ClientId = "mvc-core",
ClientName = "MVC .Net Core Client",
AllowedGrantTypes = GrantTypes.Implicit,
RequireConsent = false,
// where to redirect to after login
RedirectUris = { "https://localhost:5003/signin-oidc" },
// where to redirect to after logout
PostLogoutRedirectUris = { "https://localhost:5003/signout-callback-oidc" },
AllowedScopes = new List<string> {IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile}
},
new Client
{
ClientId = "angular_spa",
ClientName = "Angular SPA",
AllowedGrantTypes = GrantTypes.Implicit,
RequireConsent = false,
// where to redirect to after login
RedirectUris = { "http://localhost:4200/auth-callback" },
// where to redirect to after logout
PostLogoutRedirectUris = { "http://localhost:4200/" },
// cors
AllowedCorsOrigins = {"http://localhost:4200"},
AllowedScopes = new List<string> {IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile}
}
};
}
Identity Server Account configuration:
public class AccountOptions
{
public static bool AllowLocalLogin = true;
public static bool AllowRememberLogin = true;
public static TimeSpan RememberMeLoginDuration = TimeSpan.FromDays(30);
public static bool ShowLogoutPrompt = false;
public static bool AutomaticRedirectAfterSignOut = true;
public static readonly string WindowsAuthenticationSchemeName = Microsoft.AspNetCore.Server.IISIntegration.IISDefaults.AuthenticationScheme;
public static bool IncludeWindowsGroups = false;
public static string InvalidCredentialsErrorMessage = "Invalid username or password";
}
When I use the .Net framework MVC client and logout I'm redirected to Identity Server and the user is logged out without a problem but my browser gets stuck on:
LogOut page of Identity Server
The PostLogoutRedirectUri is empty on the LoggedOutViewModel but I'm not sure why. Both other clients get redirect to after logout.
Any ideas why my .Net framework MVC (.Net framework 4.6.2) client does not get redirected to?
or why its PostLogoutRedirectUri is empty on the LoggedOutViewModel?
The IdentityServer needs the id_token in order to proceed with (automatic) redirect. Because this doesn't occur, it seems the id token is not present.
Take a look at the issue here for more information.
To solve it you'll have to include the token on logout:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = n =>
{
// if signing out, add the id_token_hint
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");
if (idTokenHint != null)
n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
return Task.FromResult(0);
}
}
}
}
}
To enable automatic redirect take a look at my answer here.

Microsoft Graph in asp.net web forms access token expires - how to refresh tokens in web forms application and not MVC

I have an asp.net 4.6 web forms application (no MVC). I am updating the security in my application. I am using OpenIdConnectAuthentication to authenticate with our Azure AD. Then I pass the access token to Microsoft graph to send an email with Office 365. My token is set to expire in 60 minutes. I either need to expand the expiration to 8 hours or refresh the token. Without having MVC I am not sure how to handle this. I am looking for help with direction to take and possibly code samples.
(I original tried to utilize an MVC sample and put it into my project using a Session Token class. Once we tested with multiple users I believe I had a memory leak and it would crash in about 5 minutes.)
Startup code:
public class Startup
{
private readonly string _clientId = ConfigurationManager.AppSettings["ClientId"];
private readonly string _redirectUri = ConfigurationManager.AppSettings["RedirectUri"];
private readonly string _authority = ConfigurationManager.AppSettings["Authority"];
private readonly string _clientSecret = ConfigurationManager.AppSettings["ClientSecret"];
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieManager = new SystemWebCookieManager(),
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = _clientId,
ClientSecret = _clientSecret,
//Authority = _authority,
Authority = String.Format(_authority, domain, "/v2.0"),
RedirectUri = _redirectUri,
ResponseType = OpenIdConnectResponseType.CodeIdToken,
Scope = OpenIdConnectScope.OpenIdProfile,
UseTokenLifetime = false,
TokenValidationParameters = new TokenValidationParameters { NameClaimType = "name", RequireExpirationTime = false},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n =>
{
// Exchange code for access and ID tokens
var auth = String.Format(_authority, "common/oauth2/v2.0", "/token");
var tokenClient = new TokenClient($"{auth}", _clientId, _clientSecret);
var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, _redirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
var claims = new List<Claim>()
{
new Claim("id_token", tokenResponse.IdentityToken),
new Claim("access_token", tokenResponse.AccessToken)
};
n.AuthenticationTicket.Identity.AddClaims(claims);
},
},
});
}
}
SDK Helper:
public class SDKHelper
{
// Get an authenticated Microsoft Graph Service client.
public static GraphServiceClient GetAuthenticatedClient()
{
GraphServiceClient graphClient = new GraphServiceClient(
new DelegateAuthenticationProvider(
async (requestMessage) =>
{
string accessToken = System.Security.Claims.ClaimsPrincipal.Current.FindFirst("access_token").Value;
// Append the access token to the request.
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
// Get event times in the current time zone.
requestMessage.Headers.Add("Prefer", "outlook.timezone=\"" + TimeZoneInfo.Local.Id + "\"");
// This header has been added to identify our sample in the Microsoft Graph service. If extracting this code for your project please remove.
requestMessage.Headers.Add("SampleID", "aspnet-snippets-sample");
}));
return graphClient;
}
}
Sending Email:
GraphServiceClient graphClient = SDKHelper.GetAuthenticatedClient();
string address = emailaddress;
string guid = Guid.NewGuid().ToString();
List<Recipient> recipients = new List<Recipient>();
recipients.Add(new Recipient
{
EmailAddress = new Microsoft.Graph.EmailAddress
{
Address = address
}
});
// Create the message.
Message email = new Message
{
Body = new ItemBody
{
ContentType = Microsoft.Graph.BodyType.Text,
},
Subject = "TEST",
ToRecipients = recipients,
From = new Recipient
{
EmailAddress = new Microsoft.Graph.EmailAddress
{
Address = address
}
}
};
// Send the message.
try
{
graphClient.Me.SendMail(email, true).Request().PostAsync().Wait();
}
catch (ServiceException exMsg)
{
}
You need to request the scope offline_access. Once you've requested that, the /token endpoint will return both an access_token and a refresh_token. When your token expires, you can make another call to the /token endpoint to request a new set of access and refresh tokens.
You might find this article helpful: Microsoft v2 Endpoint Primer. In particular, the section on refresh tokens.

Getting GraphService accesstoken in ASP.NET Core 2.0 AzureAD

I'm currently trying to automatically setup a graphservice whenever my application starts. I have following code:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddAzureAd(options =>
{
Configuration.Bind("AzureAd", options);
})
.AddCookie();
services.AddMvc();
}
Inside or after the AddAzureAd I'd like to register and configure a GraphService to connect to MS AAD Graph Api https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-graph-api
Yet I have no idea how to get an accesstoken which every example speaks of. I ticked the box on the template "Read" from Graph API, so I though this would be configured automatically, sadly it isn't.
To acquire the access token in the asp.net core with OpenIdConnect protocol, we need to use OnAuthorizationCodeReceived event like code below:
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
ClientId = ClientId,
Authority = Authority,
PostLogoutRedirectUri = Configuration["AzureAd:PostLogoutRedirectUri"],
ResponseType = OpenIdConnectResponseType.CodeIdToken,
GetClaimsFromUserInfoEndpoint = false,
Events = new OpenIdConnectEvents
{
OnRemoteFailure = OnAuthenticationFailed,
OnAuthorizationCodeReceived = OnAuthorizationCodeReceived,
}
});
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedContext context)
{
// Acquire a Token for the Graph API and cache it using ADAL. In the TodoListController, we'll use the cache to acquire a token to the Todo List API
string userObjectId = (context.Ticket.Principal.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier"))?.Value;
ClientCredential clientCred = new ClientCredential(ClientId, ClientSecret);
AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectId, context.HttpContext.Session));
AuthenticationResult authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(
context.ProtocolMessage.Code, new Uri(context.Properties.Items[OpenIdConnectDefaults.RedirectUriForCodePropertiesKey]), clientCred, GraphResourceId);
// Notify the OIDC middleware that we already took care of code redemption.
context.HandleCodeRedemption();
}
More detail about acquire access_token in the asp.net core, you can refer the code sample below:
active-directory-dotnet-webapp-webapi-openidconnect-aspnetcore

Resources