I would like to create a layer for my Web API 2 Odata4 application to abstract the details of authentication so an end user can bolt on whichever Auth provider they choose. Right now the application is structured to authenticate the user in the UI using Microsoft B2C and redirect the user accordingly to the Angular2 page. All of the redirect logic to and from B2C is hard coded into the UI. At that point the token is extracted from the URL and is decoded as necessary to get username and ClientID information. When the UI makes a call to the API it passes that username and ClientID to the service which implements OAuth. The API matches the client ID with the acceptable list and serves data accordingly. Most of this process seems hard coded into both the UI and the API.
This is how the UI is grabbing the token:
sharedservice.id_token = location.hash.split('id_token=')[1];
authService.isLoggedIn = true;
fetch(sharedservice.JWKS_URL, {
method: 'GET'
}).then(res => res.json())
.then(json => {
this.getSigningKey(json.keys[0].kid, ()=>{})
})
This should only happen if the auth service has successfully authenticated the user. The token is then validated and the user information is setup.
The API gets the authentication service setup in the startup.auth.cs file under app_start and it looks like this:
// These values are pulled from web.config
public static string AadInstance = ConfigurationManager.AppSettings[" "];
public static string Tenant = ConfigurationManager.AppSettings[" "];
public static string ClientId1 = ConfigurationManager.AppSettings[" "];
public static string ClientId2 = ConfigurationManager.AppSettings[" "];
public static string ClientId3 = ConfigurationManager.AppSettings[" "];
public static string ClientId4 = ConfigurationManager.AppSettings[" "];
public static string SignUpSignInPolicy = ConfigurationManager.AppSettings[" "];
public static string DefaultPolicy = SignUpSignInPolicy;
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
// Web API routes
config.MapHttpAttributeRoutes();
ConfigureOAuth(app);
app.UseWebApi(config);
}
public void ConfigureOAuth(IAppBuilder app)
{
app.UseOAuthBearerAuthentication(CreateBearerOptionsFromPolicy(DefaultPolicy));
}
private OAuthBearerAuthenticationOptions CreateBearerOptionsFromPolicy(string policy)
{
var metadataEndpoint = string.Format(AadInstance, Tenant, policy);
TokenValidationParameters tvps = new TokenValidationParameters
{
// Accept only those tokens where the audience of the token is equal to the client ID of this app
ValidAudiences = new string[] { ClientId1, ClientId2, ClientId3, ClientId4 },
//ValidAudience = ClientId2,
AuthenticationType = Startup.DefaultPolicy
};
return new OAuthBearerAuthenticationOptions
{
// This SecurityTokenProvider fetches the Azure AD B2C metadata & signing keys from the OpenIDConnect metadata endpoint
AccessTokenFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider(metadataEndpoint))
};
}
All of the B2C configuration seems to be hard coded. What is the most acceptable way to abstract all of this information out so the app can integrate with whatever service the end user cares to use and not just B2C? I would assume such a solution would require changes to both the Angular 2 pages and the API.
Related
I'm working on a web application built using .NET Framework 4.8. The application has one side for the public and one for admins.
The application is registered on Azure, and I'm trying to use Azure AD groups to authenticate and authorize users.
I setup the Middleware for Azure authentication in a a Startup.Auth.cs partial class. The code in the partial class is shown at below.
To access the admin side, the user has to type /admin in the URL which will go to the admin controller.
I use a custom Authorize attribute in the Admin Controller with a specific Role. It's used before the initialization of the AdminController class.
The custom Authorize class code is shown below.
Locally, the application seems to be working fine and it allows people in the group specified in the authorize attribute and redirects ones without access to the Home like we want.
When we publish the code to Production, the application always takes the user back to home when they try to go to the admin side.
The redirectURI is setup to be "https://example.com/admin" and it is added to the application in Azure.
Using Authorize attribute in the admin controller allows anyone that is part of the Azure AD for the tenant.
Please let me know if I'm missing anything or what your thoughts are
Startup.Auth.cs
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 redirectUri = ConfigurationManager.AppSettings["RedirectUri"];
private static string authority = aadInstance + tenantId + "/v2.0";
public void ConfigurationAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UserOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions()
{
ClientId = clientId;
Authority = authority;
RedirectUri = redirectUri;
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthenticationFailed = (context) => {
context.HandleResponse();
context.Response.Redirect("Home/index");
return Task.FromResult(0);
}
}
});
}
}
Custom authorize attribute AuthorizeAttribute.cs:
public class AuthorizeAD : AuthorizeAttribute
{
private bool noPermission = false;
protected override bool AuthorizationCore(HttpContextBase httpContext)
{
if(!httpContext.User.Identity.IsAuthenticated)
return false;
var roles = Roles.Trim().Split(',');
if(roles.ToList().Exists(role => httpContext.User.IsInRole(role)))
{
return true;
}
else
{
noPermission = true;
return false;
}
}
protected override void HandleUnAuthorizedRequest(AuthorizationContext filterContext)
{
if(noPermission)
filterContext.Result = new RedirectResult("Home/index");
else
base.HandleUnauthorizedRequest(filterContext);
}
}
Any help, feedback, or suggestions would be great. Thank you in advance!
• You can capture the return URL using the request information instead of using the ‘custom AuthorizeAttribute’ which will make your ‘returnURL’ or redirect URI available within ‘Request.QueryString[]’ dictionary. Also, you will need to add the below in your login view to make it actionable and the below it in your login form: -
#{
ViewBag.ReturnUrl = Request.QueryString["returnUrl"];
}
#using (Html.BeginForm("Login", "Account", new {returnUrl = ViewBag.ReturnUrl}, FormMethod.Post, new{#class="form-horizontal form-material", #onsubmit="return loading_event();", #id="loginForm"}))
Please find the below SO community thread for more reference and clarification on this: -
Custom Authorize attribute with redirect to original URL
Also, since you want to use the custom ‘AuthorizeAttribute’ class with a specific role for access in the ‘AdminController’ class, you can surely use them by leveraging the role-based authorization for the actions in the ‘AdminController’ class as follows: -
String constants in ‘Auth.cs’ : -
public static class RoleConstants
{
public const string Admin = "Admin";
public const string Moderator = "Moderator";
// more roles
}
‘AdminController’ class is as below after the above constant inclusion: -
[Authorize(Roles=RoleConstants.Admin+","+RoleConstants.Moderator)]
public class AdminController : Controller
{
// ...
}
Please find the below link for more information regarding the above: -
https://www.telerik.com/blogs/creating-custom-authorizeattribute-asp-net-core
I have created an IdentityServer4 application, if I login inside that application the user claims are all good. If I login from another client application (MVC) the UserInfo endpoint doesn't return the same claims.
The IdentityServer is configured with ASP.NET Identity, so the UserProfile is already configured to return all UserClaims, like the one I created.
I don't understand why it's not showed on consent view or it's not included in UserInfo endpoint result
Please check for the below points if they can solve your issue
1.) Your Identity resource and API resource should have the required UserClaims.
2.) Check if there is some custom logic to issue requested claims for userinfo endpoint in your profile service.
public class ProfileService : IProfileService
{
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
if (context.Caller == IdentityServerConstants.ProfileDataCallers.UserInfoEndpoint)
{
//custom logic to add requested claims
context.AddRequestedClaims(claims);
}
}
}
3.) Try to make the property 'GetClaimsFromUserInfoEndpoint=true' in your MVC client AddOpenIdConnect configuration.
have you configured your IdentityResources?
Something like:
services.AddIdentityServer()
.AddInMemoryIdentityResources(GetIdentityResources())
//where
public static List<IdentityResource> GetIdentityResources()
{
// Claims automatically included in OpenId scope
var openIdScope = new IdentityResources.OpenId();
openIdScope.UserClaims.Add(JwtClaimTypes.Locale);
// Available scopes
return new List<IdentityResource>
{
openIdScope,
new IdentityResources.Profile(),
new IdentityResources.Email(),
new IdentityResource(Constants.RolesScopeType, Constants.RolesScopeType,
new List<string> {JwtClaimTypes.Role, Constants.TenantIdClaimType})
{
//when false (default), the user can deselect the scope on consent screen
Required = true
}
};
}
I am using signalR in asp.net mvc application,I want to authenticate cross
domain clients by token based authentication.I did not found complete solution for
it.
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
map.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
Provider = new QueryStringOAuthBearerProvider()
});
var hubConfiguration = new HubConfiguration
{
Resolver = GlobalHost.DependencyResolver,
};
map.RunSignalR(hubConfiguration);
});
public class QueryStringOAuthBearerProvider : OAuthBearerAuthenticationProvider
{
public override Task RequestToken(OAuthRequestTokenContext context)
{
var value = context.Request.Query.Get("access_token");
if (!string.IsNullOrEmpty(value))
{
context.Token = value;
}
return Task.FromResult<object>(null);
}
}
public class impAuthHub : Hub
{
[Authorize]
public void SendMessage(string name, string message)
{
Clients.All.newMessage(name, message);
}
}
I dont know how i will get token to pass query string to my startup class?
You will be needed to use OAuth Bearer Token authentication with SignalR. and you need to use Microsoft’s OWIN Security and ASP.NET Identity libraries then include the WebAPI and Individual Accounts security options. This is a Full- Demo
Please find the code base for working sample git , which will help you.
I am trying to setup authentication for my .net webapi using identityserver3.
This is my code in Owin.Startup of the Authentication server project
public class Startup
{
public void Configuration(IAppBuilder app)
{
// hardcoded list of clients, scopes and users
var factory = new IdentityServerServiceFactory()
.UseInMemoryClients(clients)
.UseInMemoryScopes(scopes)
.UseInMemoryUsers(users);
app.UseIdentityServer(new IdentityServerOptions
{
SigningCertificate = new X509Certificate2($#"{AppDomain.CurrentDomain.BaseDirectory}\bin\my_selfsigned_cert.pfx", ConfigurationManager.AppSettings["certificatekey"]),
RequireSsl = false,
Factory = factory
});
}
And the following is the code in my web api owin startup
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "http://localhost:45230"
});
app.UseWebApi(GlobalConfiguration.Configuration);
}
}
My authorization server seems to work when I try to login in the identity servers login page. I am also able to retrieve authorization token by posting to /connect/token
However when I use the bearer token thus received to call my webapi method below, it's always failing with error "{"Message": "Authorization has been denied for this request."}
Api -
[HttpGet]
[Authorize]
public IEnumerable<Customer> Get()
{
var customerRepository = new CustomerRepository();
return customerRepository.GetCustomers();
}
Can somebody please suggest what i am missing ?
Microsoft.Owin.Host.SystemWeb
Installed this nuget to my web api project as suggested in here and it started working !!
I am in the process of creating my own webapi in asp.net using oauth as authorization provider.
The api wil basically serve as a provider for different modules as i call them. One could be a image gallery, the other could just be a user login module with different types of users.
I have the oauth part working fine. Api users can register and then ask for a Token by calling the /Token endpoint with the login credentials.
However i now want to create another seperate user module in the api that is only accessible by apiusers that registered . I want this module to have another register and login function and have their own endpoint to login (/UserModuleToken or something like that). The users coming from the user module are different users than the Api users. So the apiusers are the actual developers that want to call specific modules in my api, and the users from the user module are users that register on the site where that module is implemented.
All of my apicontrollers wil have the [Authorize] attribute for the api user, and i want specific ones, for example some function in the user module, to be decorated with [UserModuleAuthorize] attribute.
Below you can see my api user entity model:
public class ApiUserEntity : BaseEntity
{
public string Username { get; set; }
public string Password { get; set; }
public string Email { get; set; }
public string Salt { get; set; }
public ApiUserLevel Level { get; set; }
}
The userservice function that can validate an api user:
public UserLoginResult LoginUser(ApiUserEntityLoginForm userForm)
{
// retrieve user from database
var user = _userRepository.GetUser(userForm.UserName);
if(user == null)
return _modelStateWrapper.AddError(UserLoginResult.UserNotFound, "User does not exist");
var passwordHash = PasswordHash.HashPassword(user.Salt, userForm.Password);
// check if password matches with database.
if (passwordHash != user.Password)
return _modelStateWrapper.AddError(UserLoginResult.IncorrectPassword, "Incorrect password");
return UserLoginResult.Success;
}
And calling the /Token endpoint in my webapi will call the following function of the token provider:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
// create a userloginform object :
var loginForm = new ApiUserEntityLoginForm {UserName = context.UserName, Password = context.Password};
// pass it into the login validation function of the userservice:
var loginResult = _userService.LoginUser(loginForm);
// if login result was not sucesful, return an error.
if (loginResult != UserLoginResult.Success)
{
var jsonSerialiser = new JavaScriptSerializer();
var json = jsonSerialiser.Serialize(_userService.Errors());
context.SetError("invalid_grant", json);
return;
}
// result was succesful, grant the token.
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("role", "user"));
context.Validated(identity);
}
i configure my oauth provider and define the /Token endpoint with the following function:
public static void ConfigureOAuth(IAppBuilder app, IUnityContainer container)
{
var simpleAuthorizationServerProvider = container.Resolve<SimpleAuthorizationServerProvider>();
var OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = simpleAuthorizationServerProvider
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
Now my question is if it is somehow possible to have multiple token endpoints so i can have a token for apiusers and then another one for a user that is using the custom user module and protect certain functionality based on those 2 users.
I couldnt find any information about this after an extensive amount of searching the internet. So im beginning to believe this is not good practice or not possible. If anyone would be able to point me in the right direction that would be great!
Well I believe you need to configure users authorization based on Roles, what you are trying to do is just complicating your solution.
What you can do is the following: inside method GrantResourceOwnerCredentials you need to obtain the correct role(s) for the authenticated user from the DB store i.e "Admin" and then add them as claims with type "Role" as the code below:
identity.AddClaim(new Claim(ClaimTypes.Role, "Admin"));
identity.AddClaim(new Claim(ClaimTypes.Role, "Supervisor"));
Now on your controllers that you want just user with role "Admin" to access; you need to attribute with [Authorize(Roles="Admin")] or maybe multiple roles [Authorize(Roles="Admin,User")]
This is the straightest way to achive your goal.
Btw this code from http://bitoftech.net, right? Glad to see my code samples used :)
Let me know if you need further clarifications.