I have an ASP.NET MVC application that is registered in Azure Active Directory. I am adding a new feature to the app which will require a list of all users from AAD. By putting together some code snippets I got from various MSDN docs, this is what my method to get the users looks like so far:
public async Task GetUsers()
{
string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
string clientSecret = ConfigurationManager.AppSettings["ida:ClientSecret"];
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenant)
.WithClientSecret(clientSecret)
.Build();
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var users = await graphClient.Users.Request().GetAsync();
}
However, when I run the application, I get Original exception: AADSTS7000215: Invalid client secret is provided. I double checked to make sure that the client secret that I added to web.config matches what's shown in AAD, and that the client secret hasn't expired. I even deleted the client secret and created a new one, but that didn't fix the problem either. I also ensured that the permission User.Read.All of type Application has been granted for this app. What could be causing this error, and what could be done to resolve it? Also, I'm wondering if there's a simpler way to get a list of users given that I already have authentication set up for this app using Owin.IAppBuilder. Here's what I have in my Startup.cs file:
public class Startup
{
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
string authority = string.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions()
{
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = ctx => {
ctx.HandleResponse();
ctx.Response.Redirect("/Error/messages" + ctx.Exception.Message);
return Task.FromResult(0);
}
} });
}
}
It works for me this way (using WithAuthority), I did not need any additional scopes:
string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
string tenantId = ConfigurationManager.AppSettings["ida:TenantId"];
string clientSecret = ConfigurationManager.AppSettings["ida:ClientSecret"];
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithAuthority(new Uri($"{aadInstance}{tenantId}/"))
.WithClientSecret(clientSecret)
.Build();
and then...
return await graphClient.Users
.Request()
.GetAsync();
Related
Implemented Server Client Authorization based on recent Velusia example taken from here: openiddict-samples
all ASP NET.CORE clients work fine, but unfortunately I have to implement token authorization flow for some old ASP NET 4.8 Web form applications.
Added following OWIN startup class
public class Startup
{
// These values are stored in Web.config. Make sure you update them!
private readonly string _clientId = ConfigurationManager.AppSettings["ClientId"];
private readonly string _redirectUri = ConfigurationManager.AppSettings["RedirectUri"];
private readonly string _authority = ConfigurationManager.AppSettings["AuthUrl"];
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());
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = _clientId,
ClientSecret = _clientSecret,
Authority = _authority,
RedirectUri = _redirectUri,
RequireHttpsMetadata = false,
ResponseType = OpenIdConnectResponseType.Code,
Scope = $"{OpenIdConnectScope.OpenId} profile {OpenIdConnectScope.Email} roles",
TokenValidationParameters = new TokenValidationParameters { NameClaimType = "name" , RoleClaimType = "role"},
CallbackPath = new PathString("//"), //signin-oidc
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n =>
{
var client = new HttpClient();
var tokenResponse = await client.RequestAuthorizationCodeTokenAsync(new AuthorizationCodeTokenRequest
{
Address = _authority + "/connect/token",
ClientId = _clientId,
ClientSecret = _clientSecret,
Code = n.Code,
RedirectUri = _redirectUri
});
if (!tokenResponse.IsError)
{
var claims = new List<Claim>()
{
new Claim("id_token", tokenResponse.IdentityToken),
new Claim("access_token", tokenResponse.AccessToken)
};
n.AuthenticationTicket.Identity.AddClaims(claims);
}
},
},
});
}
}
behind the login button following code:
if (!Request.IsAuthenticated)
{
HttpContext.Current.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
initially it starts fine, click on the login button and request goes to the server, I can see server login page. Once the password is entered request comes back to the client and opens following page: https://localhost/signin-oidc ... and I never get AuthorizationCodeReceived event triggered!
Anybody tried something similar?
I'm trying to create an OWIN startup class for Azure AD authentication using a sample project from Microsoft as reference. The sample is in C# but the project I'm adapting it to is in VB.NET so I need to convert the following C# to VB.NET:
public class Startup
{
// The Client ID is used by the application to uniquely identify itself to Azure AD.
string clientId = System.Configuration.ConfigurationManager.AppSettings["ClientId"];
// RedirectUri is the URL where the user will be redirected to after they sign in.
string redirectUri = System.Configuration.ConfigurationManager.AppSettings["RedirectUri"];
// Tenant is the tenant ID (e.g. contoso.onmicrosoft.com, or 'common' for multi-tenant)
static string tenant = System.Configuration.ConfigurationManager.AppSettings["Tenant"];
// Authority is the URL for authority, composed by Azure Active Directory v2 endpoint and the tenant name (e.g. https://login.microsoftonline.com/contoso.onmicrosoft.com/v2.0)
string authority = String.Format(System.Globalization.CultureInfo.InvariantCulture, System.Configuration.ConfigurationManager.AppSettings["Authority"], tenant);
/// <summary>
/// Configure OWIN to use OpenIdConnect
/// </summary>
/// <param name="app"></param>
public void Configuration(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,
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 = false
},
// OpenIdConnectAuthenticationNotifications configures OWIN to send notification of failed authentications to OnAuthenticationFailed method
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed
}
}
);
}
/// <summary>
/// Handle failed authentication requests by redirecting the user to the home page with an error in the query string
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
context.HandleResponse();
context.Response.Redirect("/?errormessage=" + context.Exception.Message);
return Task.FromResult(0);
}
}
Here's my converted VB.NET:
Public Class Startup
Private clientId As String = ConfigurationManager.AppSettings("ClientId")
Private redirectUri As String = ConfigurationManager.AppSettings("RedirectUri")
Shared tenant As String = ConfigurationManager.AppSettings("Tenant")
Private authority As String = String.Format(Globalization.CultureInfo.InvariantCulture, ConfigurationManager.AppSettings("Authority"), tenant)
Public Sub Configuration(ByVal app As IAppBuilder)
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType)
app.UseCookieAuthentication(New CookieAuthenticationOptions())
app.UseOpenIdConnectAuthentication(New OpenIdConnectAuthenticationOptions With {
.ClientId = clientId,
.Authority = authority,
.RedirectUri = redirectUri,
.PostLogoutRedirectUri = redirectUri,
.Scope = OpenIdConnectScope.OpenIdProfile,
.ResponseType = OpenIdConnectResponseType.IdToken,
.TokenValidationParameters = New TokenValidationParameters() With {
.ValidateIssuer = False
},
.Notifications = New OpenIdConnectAuthenticationNotifications With {
.AuthenticationFailed = OnAuthenticationFailed() '<---ERROR HERE
}
})
End Sub
Private Function OnAuthenticationFailed(ByVal context As AuthenticationFailedNotification(Of OpenIdConnectMessage, OpenIdConnectAuthenticationOptions)) As Task
context.HandleResponse()
context.Response.Redirect("/?errormessage=" & context.Exception.Message)
Return Task.FromResult(0)
End Function
End Class
I'm getting an error, though, on the line .AuthenticationFailed = OnAuthenticationFailed(). The error says I need to pass in the context parameter which makes sense but I'm confused about why it wasn't required in the C# version and what I'm supposed to pass in the VB.NET.
Thanks!
I ended up just writing the OnAuthenticationFailed() function inline:
Public Sub Configuration(ByVal app As IAppBuilder)
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType)
app.UseCookieAuthentication(New CookieAuthenticationOptions())
app.UseOpenIdConnectAuthentication(New OpenIdConnectAuthenticationOptions With {
.ClientId = clientId,
.Authority = authority,
.RedirectUri = redirectUri,
.PostLogoutRedirectUri = redirectUri,
.Scope = OpenIdConnectScope.OpenIdProfile,
.ResponseType = OpenIdConnectResponseType.IdToken,
.TokenValidationParameters = New TokenValidationParameters() With {
.ValidateIssuer = True
},
.Notifications = New OpenIdConnectAuthenticationNotifications With {
.AuthenticationFailed = Function(context)
context.HandleResponse()
context.Response.Redirect(HttpContext.Current.Server.MapPath("~") & "?errormessage=" & context.Exception.Message)
Return Task.FromResult(0)
End Function
}
})
End Sub
My problem is that I cannot make Azure AD auth work when creating the App registrations (in Azure portal) manually.
It all works fine if I create a new website using the MVC 5 template and let Visual Studio (2017) create a new App registration.
When I try to use the one I created it doesn't work and I'm getting this exception:
stack trace:
at
Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.d__8.MoveNext()
--- End of stack trace from previous location where exception was thrown --- at
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task
task) at
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task
task)
Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfigurationRetriever.d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown --- at
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task
task) at
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task
task) at
System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
at
Microsoft.IdentityModel.Protocols.ConfigurationManager`1.d__24.MoveNext()
Startup code I use in both:
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
private static string tenant = ConfigurationManager.AppSettings["ida:TenantId"];
private string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
RedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
}
},
TokenValidationParameters = new TokenValidationParameters
{
RequireSignedTokens = false,
},
});
}
The only difference I was able to find is that the automatically created App registration has one key and its manifest contains "passwordCredentials".
Manually created app doesn't have it. I use the IIS Express for both websites. Both Application ID and Tenant ID are correct as well as HTTPS port. All OWIN packages have the same version (in both apps). I think IIS Express somehow uses that key from above but I couldn't find where or how it's applied as my startup code is exactly the same. Any help appreciated
PS: I also tried to host it on local IIS with the same result...
I wasn't able to make it work with tenant ID (still have no idea why) but when I use tenant name it finally works with manually created App registration.
public partial class Startup
{
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
private string authority = string.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
/// <summary>
/// Configures the authentication.
/// </summary>
/// <param name="app">The application.</param>
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
RedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
}
}
});
}
}
I've been going through the same problem so am sharing my solution :
you have first to create a new secret key in your application on azure. Then add this key to your web.config
<add key="ida:ClientSecret" value="A***]E7uR****5:EEy.Wg?i" />
and in your code use :
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = (context) => private static string appKey = ConfigurationManager.AppSettings["ida:ClientSecret"];
ClientCredential credential = new ClientCredential(clientId, appKey);
string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
...
I am trying to develop a MVC ASP.NET C# Application that can support both Azure AD Authentication and Forms authentication.
I've read about it and came to the following conclusion:
I have a login-form for Forms Auth and a button which redirects me to Azure AD Login.
After I login in AD, it auto redirects me to
http://localhost/login.aspx?ReturnUrl=%2f.
Using following code:
Startup.cs
public partial class Startup
{
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
RedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
}
}
});
}
}
AccountController.cs
public void SignIn()
{
// Send an OpenID Connect sign-in request.
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" }, OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
public void SignOut()
{
// Send an OpenID Connect sign-out request.
HttpContext.GetOwinContext().Authentication.SignOut(
OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
}
public void EndSession()
{
// If AAD sends a single sign-out message to the app, end the user's session, but don't redirect to AAD for sign out.
HttpContext.GetOwinContext().Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
}
My question is why does it redirects me to http://localhost/login.aspx?ReturnUrl=%2f, giving that the app is a MVC and there is no aspx in my project.
By default, the login URL for Forms authentication is Login.aspx. You can specify the login URL for Windows Forms authentication in the web.config:
<authentication mode="Forms">
<forms loginUrl="/account/signin" defaultUrl="/" />
</authentication>
More info: FormsAuthentication.LoginUrl Property
I am trying to integrate my web service to authenticate using Azure AD. The response from Azure AD varies each time. When i open my site in firefox normal browser, the IsAuthenticated as true.
IsAuthenticatedAsTrue
Opening in a private browser, the IsAuthenticated is false.
IsAuthenticatedAsFalse
The only difference i can see is, the IsAuthenticated true is from ClaimsIdentity and IsAuthenticated false is from GenericIdentity.
The following is my startup.auth code.
public partial class Startup
{
private static string clientId = ConfigurationManager.AppSettings["ClientId"];
private static string aadInstance = ConfigurationManager.AppSettings["AADInstance"];
private static string tenantId = ConfigurationManager.AppSettings["TenantId"];
private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["PostLogoutRedirectUri"];
private static string authority = aadInstance + tenantId;
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri
});
}
}
The following is my code to send the authentication request to AzureAD
public void LoginUsingAzure()
{
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
This issue was fixed. The reason for this issue is dependency problem. Found the answer in the below stackoverflow link.
ASP.NET_SessionId + OWIN Cookies do not send to browser
Installing Kentor.OwinCookieSaver -Version 1.1.0 nuget package, solved my issue.
I am trying to reproduce this issue however failed. I am testing the web app using version 50.1.0 with clean installation.
To fix this issue, I suggest that you re-install the Firefox with this version.
Here is the test figure for your reference: