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;
Related
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
I need to get the token from a user right after of being generated. I make the request to my endpoint and I get the response with the token but throughout HTTP but I want at runtime if it is possible! Thats the base code.
if (!String.IsNullOrEmpty(hash))
{
Claim claim = new Claim(ClaimTypes.Name, hash);
Claim gg = new Claim(ClaimTypes.Role, "Role");
Claim[] claims = new Claim[] { claim, gg };
ClaimsIdentity claimsIdentity = new ClaimsIdentity(
claims, OAuthDefaults.AuthenticationType);
await base.OnGrantCustomExtension(c);
c.Validated(claimsIdentity);
}
Override the method TokenEndpointResponse(OAuthTokenEndpointResponseContext context) in your OAuthAuthorizationServerProvider
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
I am trying to understand the Asp.net Web Api Individual Accounts authentication and authorization. I have see several tutorials on the web including this one. In short, when a user agent provides username and password the API issues a token that the client will use in subsequent calls to the API for to identify itself. The user agent receives the token by making a request, typically to: http://example.com/Token. The path appears to be set in the Startup class like so:
TokenEndpointPath = new PathString("/Token")
My problem is, I can't find any controller methods that match that path. How does this work?
When you create a new Project with Individual Authentication in ASP.NET, the solution is created with an OAuth Provider to handle Authentication Request.
If you look at you solution, you should see a Providers Folder with a class ApplicationOAuthProvider.
This class implement all the logic for authenticate your members in you website.
The configuration is set at Startup to allow you to customize the url endpoint through the OAuthOption.
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
The TokenEndPoint Path properties defined the url which will fired the GrantResourceOwnerCredentials method of the GrandResourceOwnerCredentials.
If you use fiddler to authenticate and use this kind of body
grant_type=password&username=testUserName&password=TestPassword
you should pass in the following method :
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);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
where context.UserName and context.Password are set with the data used in the request.
After the identity is confirmed (here using Entity Framework and a couple userName, Password in a database), a Bearer token is sent to the caller.
This Bearer token could then be used to be authenticated for the other calls.
Regards.