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
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 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();
I have tried various authentication scenarios of Azure Active Directory across internet. All examples are focused only on Authorization by Authentication. I was looking for Authorizing the user based on Roles from my AAD App Registration.
Auth() Scenarios,
For example,
..\Controller\ArtistController.cs:
public class ArtistController : ApiController
{
[Authorize(Roles = "Admin, InternalAdmin")]
public void Post(ArtistModel model)
{
// Do admin stuff here...
}
}
..\App_Start\Startup.Auth.cs [Not working]:
public void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters
{
SaveSigninToken = true,
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"],
RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
}
});
}
..\App_Start\Startup.Auth.cs [Working]:
public void ConfigureAuth(IAppBuilder app)
{
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = ConfigHelper.ClientId,
Authority = ConfigHelper.Authority,
RedirectUri = "<<Home_Url>>",
PostLogoutRedirectUri = ConfigHelper.PostLogoutRedirectUri,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
NameClaimType = "upn",
RoleClaimType = "roles", // The claim in the Jwt token where App roles are provided.
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error/ShowError?signIn=true&errorMessage=" + context.Exception.Message);
return System.Threading.Tasks.Task.FromResult(0);
}
}
});
}
I understand that OWIN can wire any middleware to handle incoming http requests. Auth Middlewares like OpenId, WindowsBearerToken,...
Is UseOpenIdConnectAuthentication() the only correct middleware to authorize web resources by roles over UseWindowsAzureActiveDirectoryBearerAuthentication() based on this example?
Please suggest.
Yes, OpenID is the only middleware that will work for this. There is no alternative at this point to OpenID Connect.
I found the best way to set the roles is to add these roles in the manifest and then hard code the logic to give different permissions to different users.
This is the best sample that I have found for this so far. You just need to add the connection string to Azure SQL for it to work. https://github.com/Azure-Samples/active-directory-dotnet-webapp-roleclaims
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;
...
In an Action in a Controller, we have the AuthenticationResult which contains AccessToken.
We would like to do a manual login with this token that is equal to the UserCookieAuthentication owin middleware. Is that possible?
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
},
});
}
}
I have been looking at HttpContext.GetOwinContext().Authentication.SignIn() but that doesn't take any Tokens.
Looking in the Katana/Owin Github project the following TokenHelper solved it along with using the AuthenticationResponseGrant method:
result = await authContext.AcquireTokenAsync(resource, clientId, uc);
var principal = await new TokenHelper().GetValidatedClaimsPrincipalAsync(result.AccessToken);
var claimsIdentity = new ClaimsIdentity(principal.Claims, CookieAuthenticationDefaults.AuthenticationType);
var properties = new AuthenticationProperties();
HttpContext.GetOwinContext().Authentication.AuthenticationResponseGrant =
new AuthenticationResponseGrant(claimsIdentity, properties);