I'm currently setting up a new project with a Web API and a MVC UI (will eventually have a mobile UI as well which can talk to the same API).
So, I have the following plan:
User navigates to the MVC UI which takes them off to the IdentityServer4 server to log in or sign up
IdentityServer user is then added to the applications own user table in the database
Permissions can then be set on the user to limit their access
This means the identity server is just that, an identity server (and means I allow people to log in through Google, etc. without worrying about their roles and permissions).
So, to achieve the above, I need to check the user's permission on the API, NOT on the client (the client could be anything - web, phone app, JavaScript client, etc. so we can't rely on that to handle user permissions).
On the API, I have implemented a Permissionhandler and PermissionRequirement authorization policy. So, on the API controller or method, I can do something like: [Authorize(Policy = "CreateUser")]. It looks like I'll need to have a policy per system permission.
So in the authorisation handler I need to:
Get the current user's username
If they exist in the app database, check their permissions and auth or deny
If they don't exist in the app database, add them, then we can set their permissions later from the admin panel
This was going well up until I tried to request the user's username from the identity server. I understand I need to use the UserInfoClient to do that, but I can't figure out how to use the user's token/credentials to get their claims from the identity server or to at least get their User.Identity.Name.
Now I could just use the sub ID to add the user to the application database, but then whoever's managing the permissions would have no idea who that person is, so I need to use their email really.
Now in the MVC client, I can see User.Identity.Name without any problems, but in the API that value is null!?
So my question is: how on earth do I get the current user's Identity.Name, username or email from within the API?
Thanks.
Think I've cracked it now.
var mvcContext = context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext;
if (mvcContext != null)
{
// Use the UserInfo endpoint to get the user's claims
var discoveryClient = new DiscoveryClient("http://localhost:5000");
var doc = await discoveryClient.GetAsync();
var accessToken = await mvcContext.HttpContext.Authentication.GetTokenAsync("access_token");
var userInfoClient = new UserInfoClient(doc.UserInfoEndpoint);
var response = await userInfoClient.GetAsync(accessToken);
var claims = response.Claims;
}
This is detailed right at the bottom of https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies so according to Microsoft, it's the right way.
You did however spark this idea, thank you Win. I had been staring at this bloody thing for hours. All you need sometimes is someone else to say something, anything, and the curse is broken! :)
I am still open to a better way of achieving this, if anyone has one.
Cheers
I believe you already configure IdentityServerAuthentication in Web API similar like this -
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
...
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "http://UrlOfIentityServer",
RequireHttpsMetadata = false,
ApiName = "exampleapi"
});
...
}
When you make a web service call, you will need to pass the same token received from IdentityServer like this -
using (var httpClient = new HttpClient())
{
string accessToken = await HttpContext.Authentication.GetTokenAsync("access_token");
httpClient.BaseAddress = new Uri("http://UrlOfWebAPI");
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
return await httpClient.GetStringAsync("api/clock/time");
}
Related
I created an asp.net webform application using ADFS. Sign in and sign out work perfectly using the default method that comes with the template.
Eg of signout button method that is included in the template
protected void Unnamed_LoggingOut(object sender, LoginCancelEventArgs e)
{
// Redirect to ~/Account/SignOut after signing out.
string callbackUrl = Request.Url.GetLeftPart(UriPartial.Authority) + Response.ApplyAppPathModifier("~/Account/SignOut");
HttpContext.Current.GetOwinContext().Authentication.SignOut(
new AuthenticationProperties { RedirectUri = callbackUrl },
WsFederationAuthenticationDefaults.AuthenticationType,
CookieAuthenticationDefaults.AuthenticationType);
}
I have set up a timer and upon reaching zero I tried using the above code to log the user out but it doesn't work.No error thrown.
Any suggestion how to perform logout here?
What worked for me is to upon timeout to call the click event of a hidden button which in turn causes the below code to run.
// Redirect to ~/Account/SignOut after signing out.
string callbackUrl = Request.Url.GetLeftPart(UriPartial.Authority) + Response.ApplyAppPathModifier("~/Account/SignOut");
HttpContext.Current.GetOwinContext().Authentication.SignOut(
new AuthenticationProperties { RedirectUri = callbackUrl },
WsFederationAuthenticationDefaults.AuthenticationType,
CookieAuthenticationDefaults.AuthenticationType);
ADFS is the server that is responsible for authenticating the user and for managing the user session. The website/form is just using this service. It makes sense that a site that uses this service cannot have full control over it. It would make more sense to me to log out the user from the ADFS server and have that server do the heavy lifting for you.
Note the ADFS server keeps a user logged in into ADFS server, and note that when a user requests access to a resource this manifests in an access_token. They are different things. Typically when signing somebody out with a product like identity server, in order to log out you’ll need to do two things:
Revoke the access token
Log out on the authentication server, (if
that is desired, one could argue that isn’t desirable)
Note the explicit difference between session and token. You’ll notice these concepts are also in ADFS. After a quick google search you’ll find the difference between WebSSOLifetime and TokenLifetime. I would suggest configuring those to invalidate the tokens and sessions, and thereby logging the user out after an x amount of minutes.
Hope this helps.
Have you tried the above code that you have posted directly without the timer? and did it work?
Also, Try implementing the below code and see if it works.
public void LogOut()
{
var module = FederatedAuthentication.WSFederationAuthenticationModule;
module.SignOut(false);
var request = new SignOutRequestMessage(new Uri(module.Issuer), module.Realm);
Response.Redirect(request.WriteQueryString());
}
I have a nice Azure Active Directory set up with a dozen users. (All me!) So I have a Tenant ID, client ID and Client Secret.
I am also working on a simple console application that will function as a public client for this directory. This client also holds a list of usernames and passwords as this is just meant as a simple experiment. Not secure, I know. But I first need to understand how it works...
I do this:
IConfidentialClientApplication client = ConfidentialClientApplicationBuilder
.CreateWithApplicationOptions(options).Build();
And this creates my client app. Works fine.
I also get a token using "https://graph.microsoft.com/.default" and can use this to get all users as JSON:
string result = await GetHttpContentWithToken("https://graph.microsoft.com/v1.0/users",
token.AccessToken);
Although I might want it to be more user-friendly, JSON is fine for now.
How can I check if user is an authorized user?
And no, I don't want complex solutions that require various nuget packages. Just a plain and simple step-by-step explanation. I could probably Google this but I ended up with thousands of results and none were helpful... This should be easy, right?
[EDIT] I first wanted to get a list of users nut that failed because of a typo... (There's a dot before 'default'...)
It took some fooling around but it's not too difficult after all. There are a lot of libraries around Azure but it is all basically just a bunch of HTTP requests and responses. Even in a console application...
I started with making a PublicClientApplicationBuilder first:
var options = new PublicClientApplicationOptions()
{
ClientId = <**clientid**>,
TenantId = <**tenantid**>,
AzureCloudInstance = AzureCloudInstance.AzurePublic,
};
var client = PublicClientApplicationBuilder.CreateWithApplicationOptions(options).Build();
I can also create a ConfidentialClientApplication instead, but this allows me to log in interactively, if need be.
Next, set up the scopes:
var scopes = new List<string>() { "https://graph.microsoft.com/.default" };
As I wanted to log in using username and password, I have to use this:
var token = await client.AcquireTokenInteractive(scopes).ExecuteAsync();
But if I want to log in using code, I can also use this:
var password = new SecureString();
foreach (var c in <**password**>) { password.AppendChar(c); }
var token = await client.AcquireTokenByUsernamePassword(scopes, <**account**>, password).ExecuteAsync();
At this point, I'm authorized as the specified user. So, now all I need is to get whatever data I like, in JSON strings...
public static async Task<string> ExecCmd(string name, string url, string token)
{
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
string result = await GetHttpContentWithToken(url, token);
JObject json = JsonConvert.DeserializeObject(result) as JObject;
File.WriteAllText(name, json.ToString());
return result;
}
As I just want to read the data as text files, I just execute the action in using a specific and write it as formatted JSON to the file . So, using this simple method I can now use this:
await ExecCmd("Profile.txt", "https://graph.microsoft.com/v1.0/me/", token.AccessToken);
await ExecCmd("Groups.txt", "https://graph.microsoft.com/v1.0/groups", token.AccessToken);
await ExecCmd("Users.txt", "https://graph.microsoft.com/v1.0/users", token.AccessToken);
These will provide me with (1) the profile of the current user, (2) the AD groups and (3) the AD users. And probably a bit more...
I can use this ExecCmd to retrieve a lot more data, if I want to. But there's something else to keep in mind! For it all to work, you also need to configure the Azure application and make sure all access rights are assigned and approved!
So, in Azure AD you have to add an "App registration" and fiddle around with the settings... (The Azure experts are horribly shocked now, but when you want to learn, you'd just have to try and fail until you succeed...)
Also set "Default client type" to "public client" for the registered app.
In Azure, with the registered app, you also need to set the proper API permissions! Otherwise, you won't have access. And as I want access to Active Directory, I need to add permissions to "Azure Active Directory Graph". I can do this inside Azure or by using the scope when I call AcquireTokenInteractive(). For example, by using "https://graph.windows.net/Directory.Read.All" instead of "https://graph.windows.net/.default".
Once you've accessed a token interactively, you can also get more tokens using client.AcquireTokenSilent(). It gets a bit tricky from here, especially if you want to access a lot of different items. Fortunately, Active Directory is mostly the directory itself, groups, users and members.
Personally, I prefer to grant access from the Azure website but this is quite interesting.
Anyways, I wanted to authenticate users with Azure and now I know how to do this. It still leaves a lot more questions but this all basically answers my question...
I'll use this as answer, as others might find it useful...
Basically what I'm trying to do is to get recent tweets from a user and do stuff with them. I'm using Tweetinvi with PIN-based authentication, as described on the website, like this:
// Create a new set of credentials for the application
var appCredentials = new TwitterCredentials("CONSUMER_KEY", "CONSUMER_SECRET");
// Go to the URL so that Twitter authenticates the user and gives him a PIN code
var url = CredentialsCreator.GetAuthorizationURL(appCredentials);
// This line is an example, on how to make the user go on the URL
Process.Start(url);
// Ask the user to enter the pin code given by Twitter
var pinCode = Console.ReadLine();
// With this pin code it is now possible to get the credentials back from Twitter
var userCredentials = CredentialsCreator.GetCredentialsFromVerifierCode(pinCode, appCredentials);
// Use the user credentials in your application
Auth.SetCredentials(userCredentials);
Now the problem is that I have to sign in and connect to Twitter every time I launch my application via browser, which is mildly annoying. I've tried to save my authentication details in a text file (Consumer Key, Consumer Secret, Access Token, Access Token Secret), and then just insert the info into appCredentials and userCredentials, but with no results, as I keep getting TwitterNullCredentialsException. So how do I save my credentials so that I don't have to reconnect on every launch?
I am the main developer of Tweetinvi.
If you store the 4 credentials information you can then reuse them with 2 different solutions :
Auth.SetUserCredentials("CONSUMER_KEY", "CONSUMER_SECRET", "ACCESS_TOKEN", "ACCESS_TOKEN_SECRET");
// OR
var creds = new TwitterCredentials("CONSUMER_KEY", "CONSUMER_SECRET", "ACCESS_TOKEN", "ACCESS_TOKEN_SECRET");
Auth.SetCredentials(creds);
The documentation might help you set up your application : https://github.com/linvi/tweetinvi/wiki/Introduction
To authenticate with AzureAD I put the folling Code in an Console Application
private static async Task<string> GetAuthTokenAsync(string tendent,string AppIdUri , string ClientID)
{
/*
<add key="ida:Audience" value="https://mehler.ws/ToDoWebApi" />
<add key="ida:ClientID" value="f0e91727-3edd-4b00-9630-591166a74e4b" />
*/
var authContext = new AuthenticationContext(string.Format("https://login.windows.net/{0}", tendent));
AuthenticationResult result = authContext.AcquireToken(AppIdUri, ClientID , new Uri(Settings.Default.WebApiReplyAdress));
return result.CreateAuthorizationHeader();
}
the Method AcquireToken Shows a Screen where I am asked to Input my Credentials.
I accidently selected the Account I log in with in Windows 10. Now the Screen doesn't show up any more an the application uses my Windows 10 Account automatically. Does anyone know how to fix this Problem, so that the Screen shows up again?
Token's are cached to alleviate complexity in your app. You will need to clear the token cache if you want the user to log back in... typically you would setup a logout function.
More information on token cache: http://www.cloudidentity.com/blog/2013/10/01/getting-acquainted-with-adals-token-cache/
How to logout:
authContext.TokenCache.Clear();
string requestUrl = "https://login.microsoftonline.com/{0}/oauth2/logout?post_logout_redirect_uri={1}";
Task.Run(async () =>
{
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
var response = await client.SendAsync(request);
});
{0} - Fully qualified name of your Azure Active Directory e.g.
yourad.onmicrosoft.com or tenant id.
{1} - The URL of your application where a user must be redirected
back after the logout is complete. This should be properly URL
encoded.
An easy way is to pass to AcquireToken PromptBehavior.Always, there's an overload for that. That will cause ADAL to ignore the cache and will ask the service for a clean prompt
I found a very simple Solution myself to once clear the Cache.
Delete Cookies in Internet Explorer / Edge ;-)
Thanks anyway for the Answers hot to implement proper Logout Code and Force Prompt for Login allways.
I am trying to use box api in an asp.net web application.
Based on the search there are two options to access box account;
By downloading the Box.V2 package using below link containing the required dlls and use that in our application
By using Box SDK containing code and reference that inside our application. Using this approach we can debug the Box.V2 code by adding the project to our solution.
Correct me if I am wrong.
So, I am trying to implement the second approach. Can someone help me move forward by specifying the steps to be taken, minimum .net framework requirement, etc.
Good question, GitHub samples does not mention about the Web (Asp.Net).
It's possible and it looks pretty easy to do once you figure out the the way,
I have seen some answers for Windows apps trying to manually build the authorization URLs etc, but there is an easier way to do it.
Here's how to do it with OAuth,
Install nuget
PM> Install-Package Box.V2
Get the Authcode (this is what's been missing in most examples)
public async Task<ActionResult> Connect()
{
var clientId = "xxxxx";
var clientSecret = "xxxxxx";
var redirectUri = new Uri("http://localhost:xxxx/Home/AuthCallBackAsync");//Your call back URL
var config = new BoxConfig(clientId, clientSecret, redirectUri);
return Redirect(config.AuthCodeUri.ToString());
}
Interesting thing is that the "config" object generates the AuthCodeUri.
This will redirect the user to Consent screen and ask the user to sign in. Once the user "Grants Access" you will get the "Authcode" for your call back URL which can be used to generate accesstoken.
Handle the Auth Callback response
public async Task<ActionResult> AuthCallbackAsync()
{
NameValueCollection parms = Request.QueryString;
var authCode = parms["code"]
//Get "config" - you can store this in session or in a cache.
var config = new BoxConfig(clientId, clientSecret, redirectUri);
var client = new BoxClient(config);
await client.Auth.AuthenticateAsync(authCode);
//Now you will get the accesstoken and refresh token
var accessToken = client.Auth.Session.AccessToken;
var refreshToken = client.Auth.Session.RefreshToken;
//Ready to consume the API
var user = await client.UsersManager.GetCurrentUserInformationAsync();
-------More Api Calls---
}