I have the following setup:
var authContext = new AuthenticationContext("https://login.microsoftonline.com/common");
string redirectUri = Url.Action("Authorize", "Planner", null, Request.Url.Scheme);
Uri authUri = authContext.GetAuthorizationRequestURL("https://graph.microsoft.com/", SettingsHelper.ClientId,
new Uri(redirectUri), UserIdentifier.AnyUser, null);
// Redirect the browser to the Azure signin page
return Redirect(authUri.ToString());
This takes you to:
// Get the 'code' parameter from the Azure redirect
string authCode = Request.Params["code"];
// The same url we specified in the auth code request
string redirectUri = Url.Action("Authorize", "Planner", null, Request.Url.Scheme);
// Use client ID and secret to establish app identity
ClientCredential credential = new ClientCredential(SettingsHelper.ClientId, SettingsHelper.ClientSecret);
//FileTokenCache at specific location
TokenCache fileTokenCache = new FilesBasedAdalV3TokenCache("C:\\temp\\justin.bin");
AuthenticationContext authContext = new AuthenticationContext(SettingsHelper.AzureADAuthorityTenantID, fileTokenCache);
AuthenticationResult authResult = null;
try
{
// Get the token silently first
authResult = await authContext.AcquireTokenSilentAsync(SettingsHelper.O365UnifiedResource, credential, UserIdentifier.AnyUser);
}
catch (AdalException ex)
{
authContext = new AuthenticationContext(SettingsHelper.AzureADAuthority, fileTokenCache);
authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(authCode, new Uri(redirectUri), credential, SettingsHelper.O365UnifiedResource);
}
The token is successfully saved in the file and it seems that it is also being successfully retrieved. However the silent token acquisition still gives an exception to get token first using the non silent function. What am I missing please?
Note that O365UnifiedResource is set to https://graph.microsoft.com/
solved this by using
new UserIdentifier("<email address used to login microsoft apps>", UserIdentifierType.RequiredDisplayableId)
instead of
UserIdentifier.AnyUser
and fixed the client ID to be the APP ID as specified in the registration of the app
Related
Im trying to sign in a user in web api without using their Username/Password combination. I have access to the User object for the user but need to "log them in" and return the access token to the client application for subsequent requests.
I've tried variations on the following but with no luck, the UserManager object is disposed as soon as I call GenerateUserIdentityAsync the first time which causes it to fail for the cookiesIdentity and its warning me that my cast OAuthGrantResourceOwnerContextCredentials is a "Suspicious type conversion or check" but the code never reaches that line anyway; this is what Ive tried, which was taken and modified from the GrantResourceOwnerCredentials method of my ApplicationOAuthProvider class. Incidentally my Token end point works perfectly with the usual username, password and grant_type request.
var user = // Super secret way of getting the user....;
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
// UserManager is not null at this point
var oAuthIdentity = await user.GenerateUserIdentityAsync(UserManager,
OAuthDefaults.AuthenticationType);
// UserManager is null at this point and so throws exception
var cookiesIdentity = await user.GenerateUserIdentityAsync(UserManager,
CookieAuthenticationDefaults.AuthenticationType);
var properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
((OAuthGrantResourceOwnerCredentialsContext)HttpContext.Current.GetOwinContext().Request.Context)
.Validated(ticket);
HttpContext.Current.GetOwinContext().Request.Context.Authentication.SignIn(cookiesIdentity);
In essence all I want to do is return an access token for a user for which I do not have the username and password but a "secret" that I want to use instead of username password. Is there a way?
OK so after much digging I found this article that helped me put together this code which works like a charm:
var user = // Super secret method of getting the user
var tokenExpiration = TimeSpan.FromDays(1);
ClaimsIdentity identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));
identity.AddClaim(new Claim("role", "user"));
var props = new AuthenticationProperties()
{
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.Add(tokenExpiration),
};
var ticket = new AuthenticationTicket(identity, props);
var accessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
JObject tokenResponse = new JObject(
new JProperty("userName", user.UserName),
new JProperty("access_token", accessToken),
new JProperty("token_type", "bearer"),
new JProperty("expires_in", tokenExpiration.TotalSeconds.ToString()),
new JProperty(".issued",
ticket.Properties.IssuedUtc.GetValueOrDefault().DateTime.ToUniversalTime()),
new JProperty(".expires",
ticket.Properties.ExpiresUtc.GetValueOrDefault().DateTime.ToUniversalTime()));
return tokenResponse;
I have a web api in my organization built with aspnet core. We want to publish that api to be consumed by an android app, a mvc5 app and an aspnet core mvc6 app. How can I configure the web api in azure so that the apps that consume it don't ask to login. The web apps, are already protected with azure, but when I protect the web api with azure I get a 401 when I make a request to it. I don't know how to configure the app in azure or the code I must configure in the api. I've read a lot but I don't find a way to acomplish this. All I want is to login in my web app, and the web app starts to ask data to the web api through ajax. I should send in the ajax request some sort of bareer token, but i don`t know what config i must do in azure and in the apps. I hope you can help me.
After you protected the web API with Azure AD, we need to send to access token with request for the web API for authorization. And we can get the access token when the users call the web API from web app. Here is the code to acquire the token in the web app for your reference:
public async Task<IActionResult> Index()
{
AuthenticationResult result = null;
List<TodoItem> itemList = new List<TodoItem>();
try
{
string userObjectID = (User.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier"))?.Value;
AuthenticationContext authContext = new AuthenticationContext(Startup.Authority, new NaiveSessionCache(userObjectID, HttpContext.Session));
ClientCredential credential = new ClientCredential(Startup.ClientId, Startup.ClientSecret);
result = await authContext.AcquireTokenSilentAsync(Startup.TodoListResourceId, credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
//
// Retrieve the user's To Do List.
//
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, TodoListBaseAddress + "/api/todolist");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = await client.SendAsync(request);
//
// Return the To Do List in the view.
//
if (response.IsSuccessStatusCode)
{
List<Dictionary<String, String>> responseElements = new List<Dictionary<String, String>>();
JsonSerializerSettings settings = new JsonSerializerSettings();
String responseString = await response.Content.ReadAsStringAsync();
responseElements = JsonConvert.DeserializeObject<List<Dictionary<String, String>>>(responseString, settings);
foreach (Dictionary<String, String> responseElement in responseElements)
{
TodoItem newItem = new TodoItem();
newItem.Title = responseElement["title"];
newItem.Owner = responseElement["owner"];
itemList.Add(newItem);
}
return View(itemList);
}
else
{
//
// If the call failed with access denied, then drop the current access token from the cache,
// and show the user an error indicating they might need to sign-in again.
//
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
var todoTokens = authContext.TokenCache.ReadItems().Where(a => a.Resource == Startup.TodoListResourceId);
foreach (TokenCacheItem tci in todoTokens)
authContext.TokenCache.DeleteItem(tci);
ViewBag.ErrorMessage = "UnexpectedError";
TodoItem newItem = new TodoItem();
newItem.Title = "(No items in list)";
itemList.Add(newItem);
return View(itemList);
}
}
}
catch (Exception ee)
{
if (HttpContext.Request.Query["reauth"] == "True")
{
//
// Send an OpenID Connect sign-in request to get a new set of tokens.
// If the user still has a valid session with Azure AD, they will not be prompted for their credentials.
// The OpenID Connect middleware will return to this controller after the sign-in response has been handled.
//
return new ChallengeResult(OpenIdConnectDefaults.AuthenticationScheme);
}
//
// The user needs to re-authorize. Show them a message to that effect.
//
TodoItem newItem = new TodoItem();
newItem.Title = "(Sign-in required to view to do list.)";
itemList.Add(newItem);
ViewBag.ErrorMessage = "AuthorizationRequired";
return View(itemList);
}
//
// If the call failed for any other reason, show the user an error.
//
return View("Error");
}
And below is the code sample which use JwtBearerAppBuilderExtensions to add OpenIdConnect Bearer authentication capabilities to an HTTP application pipeline for the web API to verify the token:
public class Startup
{
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// Add the console logger.
loggerFactory.AddConsole(LogLevel.Debug);
// Configure the app to use Jwt Bearer Authentication
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Authority = String.Format(Configuration["AzureAd:AadInstance"], Configuration["AzureAD:Tenant"]),
Audience = Configuration["AzureAd:Audience"],
});
}
}
The full code sample you can refer here.
Note: to run this sample successfully, we need to modify the Title and Owner to lowercase title, owner in the ToDoController of web app:
foreach (Dictionary<String, String> responseElement in responseElements)
{
TodoItem newItem = new TodoItem();
newItem.Title = responseElement["title"];
newItem.Owner = responseElement["owner"];
itemList.Add(newItem);
}
You can use Azure OpenIdConnect for federated authentication. A good article from microsoft below -
Calling a web API in a web app using Azure AD and OpenID Connect
I am using OAuth in ASP.NET Web Api to return access token to the caller of the application.
I have inherited my OAuth provider class from OAuthAuthorizationServerProvider and once the user is authenticated inside the GrantResourceOwnerCredentials function, I want to read the generated access token, create it's hash with some salt value and then add the created hash into a cookie.
Below is the simplified definition of my GrantResourceOwnerCredentials function.
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager, OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager, CookieAuthenticationDefaults.AuthenticationType);
//Add claims required on client side.
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
//Generate the token behind the scene for given ticket
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
SetCsrfCookie(context);
}
private void SetCsrfCookie(OAuthGrantResourceOwnerCredentialsContext context)
{
var accessToken = "<READ THE GENERATED ACCESS TOKEN HERE>"; //<------ How?
if(string.IsNullOrEmpty(accessToken)) return;
var csrfToken = Helper.GetHash(accessToken);
context.Response.Cookies.Append("XSRF-TOKEN", csrfToken, new CookieOptions {HttpOnly = false});
}
I am facing two issues here.
First one is how to read the generated access token in the SetCsrfCookie function in the code above.
Generated cookie is not received on the client side.
I know its possible to intercept the response in a some OwinMiddleware inherited class and then I may be able to generate the required cookie and attach to the response but first I have not tried that and secondly, it seems better option to handle this case inside my OAuth provider class as some people suggest that deriving from the OwinMiddleware is not a good practice.
I finally managed to fix the cookie issue by adding the below line of code on angular side
$httpProvider.defaults.withCredentials = true;
On the Web Api side I just set the Access-Control-Allow-Credentials response header to true inside the WebApiConfig.Register method like below:
var cors = new EnableCorsAttribute(ConfigurationManager.AppSettings["ALLOWED_ORIGIN"], "*", "*")
{
SupportsCredentials = true
};
config.EnableCors(cors);
This solved my cookie problem.
For accessing the generated access token I inherited a class from OwinMiddleware and inside the Invoke function I access the response body to read the access token like below:
public override async Task Invoke(IOwinContext context)
{
var path = context.Request.Path;
var stream = context.Response.Body;
var buffer = new MemoryStream();
context.Response.Body = buffer;
await Next.Invoke(context);
var reqStream = new StreamReader(context.Request.Body);
reqStream.BaseStream.Position = 0;
var data = reqStream.ReadToEnd();
if (path.Equals(new PathString("/token"),StringComparison.CurrentCultureIgnoreCase))
{
buffer.Seek(0, SeekOrigin.Begin);
var reader = new StreamReader(buffer);
var responseBody = await reader.ReadToEndAsync();
//check if the response body contains access token if so then do your processing
}
buffer.Seek(0, SeekOrigin.Begin);
await buffer.CopyToAsync(stream);
}
I've been doing a lot tinkering around with the authentication stuff using the .NET libraries provided by Google.
We have both a desktop and web-app side, and what we want to achieve is to authenticate ONCE, either on the desktop or the web side, and store the refresh token, and reuse it both on the web side and the desktop side.
So the situation is like so, on the desktop side, when there's no saved existing AccessToken's and RefreshToken's, we will ask the user to authenticate via this code:
using (var stream = new FileStream("client_secrets_desktop.json", FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(GoogleClientSecrets.Load(stream).Secrets,
new[] { GmailService.Scope.GmailReadonly, GmailService.Scope.GmailCompose },
"someemail#gmail.com", CancellationToken.None);
}
In this case the Client ID and Secret is of an Application type Installed Application.
On the web-application side, if there's also no refresh token yet then I'm using DotNetOpenAuth to trigger the authentication, here's the code snippet:
const string clientID = "someclientid";
const string clientSecret = "somesecret";
const string redirectUri = "http://localhost/Home/oauth2callback";
AuthorizationServerDescription server = new AuthorizationServerDescription
{
AuthorizationEndpoint = new Uri("https://accounts.google.com/o/oauth2/auth"),
TokenEndpoint = new Uri("https://accounts.google.com/o/oauth2/token"),
ProtocolVersion = ProtocolVersion.V20
};
public ActionResult AuthenticateMe()
{
List<string> scope = new List<string>
{
GmailService.Scope.GmailCompose,
GmailService.Scope.GmailReadonly,
GmailService.Scope.GmailModify
};
WebServerClient consumer = new WebServerClient(server, clientID, clientSecret);
// Here redirect to authorization site occurs
OutgoingWebResponse response = consumer.PrepareRequestUserAuthorization(
scope, new Uri(redirectUri));
response.Headers["Location"] += "&access_type=offline&approval_prompt=force";
return response.AsActionResult();
}
public void oauth2callback()
{
WebServerClient consumer = new WebServerClient(server, clientID, clientSecret);
consumer.ClientCredentialApplicator =
ClientCredentialApplicator.PostParameter(clientSecret);
IAuthorizationState grantedAccess = consumer.ProcessUserAuthorization(null);
string accessToken = grantedAccess.AccessToken;
}
Here is where I want to confirm my suspicions.
When there is a RefreshToken that exists, we use the following code snippet to call the Gmail API's
UserCredential uc = new UserCredential(flow, "someemail#gmail.com", new TokenResponse()
{
AccessToken = "lastaccesstoken",
TokenType = "Bearer",
RefreshToken = "supersecretrefreshtoken"
});
var refreshState = await uc.RefreshTokenAsync(CancellationToken.None);
var svc = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = uc,
ApplicationName = "Gmail Test",
});
Here's the thing I noticed is that, for me to be able to use the refresh token to refresh from either the desktop or the web side, the refresh token needs to be generated through the same client ID/secret combination. I've tested it and it seems like it's fine if we use Installed application as the application type for the Client ID for both the desktop and the web, but my question I guess is, these application type's for the client IDs, do they matter so much?
Am I doing anything wrong to do it this way?
Thanks in advance
When I call my WEB API from my Console Application, I encounter:
The remote server returned an error: (401) Unauthorized.
This application runs in Interanet (Windows Authentication)
Uri uri = new Uri("http://myServer/api/main/foo");
WebClient client = new WebClient();
client.Credentials = CredentialCache.DefaultCredentials;
using (Stream data = client.OpenRead(uri))
{
using (StreamReader sr = new StreamReader(data))
{
string result = sr.ReadToEnd();
Console.WriteLine(result);
}
}
Updated
If I replace
client.Credentials = CredentialCache.DefaultCredentials;
with this line
client.Credentials = new NetworkCredential( username, password);
it works fine but I need the current credential to be set automatically.
Any idea?
Thanks in advance ;)
You use the default windows credentials here
client.Credentials = CredentialCache.DefaultCredentials;
Specify the credential that you want to authenticate using the following code:
var credential = new NetworkCredential(, , );
serverReport.ReportServerCredentials.NetworkCredentials = credential;
following line is the cause of this behaviour :
client.Credentials = CredentialCache.DefaultCredentials;
Actually this line assigns the credentials of the logged in user or the user being impersonated ( which is only possible in web applications ) , so what I believe is that you have to provide credentials explicitly (http://msdn.microsoft.com/en-us/library/system.net.credentialcache(v=vs.110).aspx) , thanks.