How to convert bearer token into authentication cookie for MVC app - c#

I have a 3 tier application structure. There is a cordova js application for end-users, an implementation of identityserver3 which serves as the OpenID authority, and an MVC app which will be access through an in-app browser in the cordova application.
The starting entry point for users is the cordova app. They login there via an in-app browser and can then access application features or click a link to open the in-app browser and visit the MVC app.
Our strategy for securing the MVC website was to use bearer token authentication, since we already logged in once from the app and didn't want to prompt the user to login again when they were directed to the MVC app:
app.Map("/account", account =>
{
account.UseIdentityServerBearerTokenAuthentication(new IdentityServer3.AccessTokenValidation.IdentityServerBearerTokenAuthenticationOptions()
{
Authority = "https://localhost:44333/core",
RequiredScopes = new string[] { "scope" },
DelayLoadMetadata = true,
TokenProvider = new QueryStringOAuthBearerProvider(),
ValidationMode = ValidationMode.ValidationEndpoint,
});
}
Since persisting the access_token on the query string is painful, I implemented a custom OAuthBearerAuthenticationProvider:
public class QueryStringOAuthBearerProvider : OAuthBearerAuthenticationProvider
{
private static ILog logger = LogManager.GetLogger(typeof(QueryStringOAuthBearerProvider));
public override Task RequestToken(OAuthRequestTokenContext context)
{
logger.Debug($"Searching for query-string bearer token for authorization on request {context.Request.Path}");
string value = GetAccessTokenFromQueryString(context.Request);
if (!string.IsNullOrEmpty(value))
{
context.Token = value;
//Save the token as a cookie so the URLs doesn't need to continue passing the access_token
SaveAccessTokenToCookie(context.Request, context.Response, value);
}
else
{
//Check for the cookie
value = GetAccessTokenFromCookie(context.Request);
if (!string.IsNullOrEmpty(value))
{
context.Token = value;
}
}
return Task.FromResult<object>(null);
}
[cookie access methods not very interesting]
}
This works, and allows the MVC application to not have to persist the access token into every request, but storing the access token as just a generic cookie seems wrong.
What I'd really like to do instead is use the access token to work with the OpenID endpoint and issue a forms-auth style cookie, which responds to logout. I found that I can add account.UseOpenIdConnectAuthentication(..) but if I authenticate via access_token, the OpenIdConnectAuthentication bits are simply skipped. Any ideas?

You don't -- access tokens are designed to be used to call web apis. You use the id_token from OIDC to authenticate the user and from the claims inside you issue your local authentication cookie. The Microsoft OpenIdConnect authentication middleware will do most of this heavy lifting for you.

Related

Authentication against local AD in the Angular application

I've been developing an Angular app with .NET Core backend (services). The task is to enable an integrated authentication, i.e. make it work with the local user seamlessly, so I login to my (connected to a local AD) machine once and the web application lets me in without the necessity to login a second time. We've been working with Identity Server 4 and intended to implement this scenario using it.
There is a little documentation on the official website concerning the Windows Authentication (e.g. against Active directory): http://docs.identityserver.io/en/latest/topics/windows.html but it doesn't explain much. As per my info, to make this scenario work the browser utilizes either Kerberos or NTLM. Neither of them is mentioned in the IS4 docs. I'm lacking the understanding of how the local credentials are getting picked up and how IS4 'knows' the user belongs to AD? How I can make sure only the users from a specific domain have access to my app?
I found some working stuff here https://github.com/damienbod/AspNetCoreWindowsAuth but questions remain the same. Even though I was able to get to the app with my local account I don't understand the flow.
I expect the user utilizing the app in the local network to log-in to the app without entering the login/password (once he's already logged in to the Windows). Is this something achievable?
Identity Server is intended to serve as an Identity Provider, if you need to talk with your AD you should see the Federation Gateway architecture they propose using the IAuthenticationSchemeProvider. Where Identity Server acts as an endpoint and talks with your AD.
This is the link:
http://docs.identityserver.io/en/latest/topics/federation_gateway.html
You have the control to programmatically reach your AD and pass the correct credentials to get the authentication. That step should be done in your Identity Server. After you get authenticated you should get redirected to your application again.
About your last question, the answer is yes, if you have your website hosted on an intranet and you have the access to your AD, you don't need to capture your credentials as user input, you can programmatically reach the AD as I said.
Bellow is the code I use to connect with my active directory
On the ExternalController class, you get when you use IdentityServer, you have this:(I don't remember at the top of my head how much I changed from the original code, but you should get the idea)
/// <summary>
/// initiate roundtrip to external authentication provider
/// </summary>
[HttpGet]
public async Task<IActionResult> Challenge(string provider, string returnUrl)
{
if (string.IsNullOrEmpty(returnUrl)) returnUrl = "~/";
// validate returnUrl - either it is a valid OIDC URL or back to a local page
if (Url.IsLocalUrl(returnUrl) == false && _interaction.IsValidReturnUrl(returnUrl) == false)
{
// user might have clicked on a malicious link - should be logged
throw new Exception("invalid return URL");
}
if (AccountOptions.WindowsAuthenticationSchemeName == provider)
{
// windows authentication needs special handling
return await ProcessWindowsLoginAsync(returnUrl);
}
else
{
// start challenge and roundtrip the return URL and scheme
var props = new AuthenticationProperties
{
RedirectUri = Url.Action(nameof(Callback)),
Items =
{
{ "returnUrl", returnUrl },
{ "scheme", provider },
}
};
return Challenge(props, provider);
}
}
private async Task<IActionResult> ProcessWindowsLoginAsync(string returnUrl)
{
// see if windows auth has already been requested and succeeded
var result = await HttpContext.AuthenticateAsync(AccountOptions.WindowsAuthenticationSchemeName);
if (result?.Principal is WindowsPrincipal wp)
{
// we will issue the external cookie and then redirect the
// user back to the external callback, in essence, testing windows
// auth the same as any other external authentication mechanism
var props = new AuthenticationProperties()
{
RedirectUri = Url.Action("Callback"),
Items =
{
{ "returnUrl", returnUrl },
{ "scheme", AccountOptions.WindowsAuthenticationSchemeName },
}
};
var id = new ClaimsIdentity(AccountOptions.WindowsAuthenticationSchemeName);
id.AddClaim(new Claim(JwtClaimTypes.Subject, wp.Identity.Name));
id.AddClaim(new Claim(JwtClaimTypes.Name, wp.Identity.Name));
// add the groups as claims -- be careful if the number of groups is too large
if (AccountOptions.IncludeWindowsGroups)
{
var wi = wp.Identity as WindowsIdentity;
var groups = wi.Groups.Translate(typeof(NTAccount));
var roles = groups.Select(x => new Claim(JwtClaimTypes.Role, x.Value));
id.AddClaims(roles);
}
await HttpContext.SignInAsync(
IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme,
new ClaimsPrincipal(id),
props);
return Redirect(props.RedirectUri);
}
else
{
// trigger windows auth
// since windows auth don't support the redirect uri,
// this URL is re-triggered when we call challenge
return Challenge(AccountOptions.WindowsAuthenticationSchemeName);
}
}
If you want to use Azure AD, I would recommend you to read this article:
https://damienbod.com/2019/05/17/updating-microsoft-account-logins-in-asp-net-core-with-openid-connect-and-azure-active-directory/
Not sure if it's what you want, but I would use the Active Directory Federation Services to configure an OAuth2 endpoint and obtain the user token in the .Net Core Web App.
Isn't NTLM authentication support limited on non Microsoft browsers?
OAuth2 have the advantage of using only standard technologies.
One way to do it is to have 2 instances of the app deployed.
The first one is configured to use Windows Authentication and the other one uses IS4.
ex:
yoursite.internal.com
yoursite.com
Your local DNS should redirect traffic internally from yoursite.com to yoursite.internal.com
yoursite.internal.com will be the one configured to use AD authentication. You should have a flag in your appsettings.json to indicate if this instance is a AD auth or IS4 auth.
The downside of this solution is that you have to deploy 2 instances

Where should i store expiring REST API authentication token?

We have a OAuth API which provides expiring tokens to authenticate REST APIs in our application.
What I am trying to achieve
While application is running on server when first request comes through, get the expiring token, expiry date from OAuth API and store in the application somewhere and use that token until that expiry date and request for another token after that.This Token should be used Globally across the application until it expires.
What I have Done
Setup a Method which will get token from Oauth API and writing it to web.config file as App settings with the expiry date. whenever a request comes through to hit the REST API it will check if the token is available and not expired from web.config and return the Token. if the Token is not available or expired it will get a new token from OAuth API.
Web.Config
<appSettings>
<add key="Token" value="" />
<add key="ExpiryDate" value="" />
</appSettings>
CS file
public RESTAPI GetData()
{
string Token = GetToken();
//use this Token to Authenticate REST API
}
public string GetToken()
{
string Token = ConfigurationManager.AppSettings["Token"];
DateTime ExpiryDate = DateTime.parse(ConfigurationManager.AppSettings["ExpiryDate"]);
if(Token == "" || ExpiryDate<=DateTime.now)
{
RefreshToken();
}
return ConfigurationManager.AppSettings["Token"];
}
public void RefreshToken()
{
//Consider OauthObject as object returned from Oauth API with Token and expiry date
ConfigurationManager.AppSettings["Token"] = OauthObject.Token;
ConfigurationManager.AppSettings["ExpiryDate"] = OauthObject.ExpiryDate.toString();
}
I want one token to be distributed across the application for all users until it is expired. does it work this way if i want to do that? or any other suggestions please.
Note: ASP.Net web application written in C#.
For .Net Core you can store the authentication token in memory using the IMemoryCache interface, which you can inject easily to the consumer service using the build-in DI container.
That way you can store it within memory only for the expired time span that correlated with Expires_in, so afterwards it will be deleted automatically.
class RestConsumerService
{
private readonly IMemoryCache _memoryCache;
RestConsumerService(IMemoryCache memoryCache)
{
_memoryCache = memoryCache
}
string GetOAuthToken()
{
if (_memoryCache.TryGetValue<string>("MyTokenKey", out string access_token)) //You can store the T object as well.
{
return access_token;
}
//Do Api OAuth call
//...
OAuthRespone response = fetchAccessToken();
_memoryCache.Set("MyTokenKey", response.Access_token, new TimeSpan(0, 0, result.Expires_in));
return response.Access_token;
}
}
I'm not really sure why you want to store the access token in the first place. Why not just keep the token in memory while its valid?
From what you wrote, it looks like you want your application to act on its own behalf for any user. In that case, the OAuth2 client credentials flow is your best option.
Also, it may be better to just use the token until the API returns an HTTP 401 error and then renew the token and retry the API. If you store the expiration time, you have to account for clock-skew between machines. The issued token may be encrypted, in which case you cannot get the expiration time.

Sign In User / Authenticate User with AWS Cognito

I am trying to sign in a user server side on asp.net Core 2
I have registered a user and confirmed with a verification link but now I am struggling to sign that user into my application. It's a shame the documentation for c# is so poor!
User Pool Config:
App Client: Enable sign-in API for server-based authentication (ADMIN_NO_SRP_AUTH) - checked
Here's the code:
public async Task<bool> SignInUserAsync(CognitoUser user)
{
var provider = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(),
RegionEndpoint.GetBySystemName("eu-west-2"));
try
{
var authReq = new AdminInitiateAuthRequest
{
AuthFlow = AuthFlowType.ADMIN_NO_SRP_AUTH,
UserPoolId = _poolId,
ClientId = _clientId
};
authReq.AuthParameters.Add("USERNAME", user.Email);
authReq.AuthParameters.Add("PASSWORD", user.Password);
AdminInitiateAuthResponse authResp = await provider.AdminInitiateAuthAsync(authReq);
return true;
}
catch
{
return false;
}
}
The error that returns is Missing Authentication Token but I can't work out where the token needs to be set / has been given to me.
Is it something with my AmazonCognitoIdentityProviderClient settings or perhaps App client settings under the
AWS > User Pools > App Intergration > App Client Settings?
AdminInitiateAuth API is meant to be called from a back end which has access to developers IAM credentials. Since you are trying to call this with AnonymousAWSCredentials, you are getting Missing Authentication Token error.
Cognito User Pools does not yet have native support for C#. You should integrate Cognito User Pools in your C# app using the hosted auth pages instead of native API calls.
Try changing your provider to:
var provider = new AmazonCognitoIdentityProviderClient();

need to pass a default emailid in azure active directory authentication

I am doing Azure Active Directory authentication and using openidconnect for authentication. My application has it own login page and I am trying to redirect the user as soon as they type user id in my login page.
But I am unable to pass userid from my login page to azure login page. I am using following code for
calling azure login page and it redirected correctly but I am not able to pass any default login id which should be displayed on the azure login page like "abc#microsoft.com".
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" });
You need to add a login_hint query string parameter in the redirect to the authorization server. Here is a post which describes this (in the context of Google login, which also uses OpenID Connect):
http://forums.asp.net/t/1999402.aspx?Owin+pass+custom+query+parameters+in+Authentication+Request
In your case, I suggest you try the following:
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties
{
RedirectUri = "/",
Dictionary =
{
{ "login_hint", "abc#microsoft.com" }
}
});

Verify Access Token - Asp.Net Identity

I'm using ASP.Net Identity to implement external logins. After user logins in with Google I get google's external access token. I then make a second api call to ObtainLocalAccessToken() which trades the external access token for a new local one.
ObtainLocalAccessToken() calls VerifyExternalAccessToken() which verifies the external access token with the provider by manually making http calls and parsing the user_id.
How can I leverage ASP.NET identity to remove the entire method VerifyExternalAccessToken()?
I believe that's what [HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)] is for isn't it? I want to decorate ObtainLocalAccessToken() endpoint with that attribute and send the external_access_token in the header ({'Authorization' : 'Bearer xxx' }), and it should populate User.Identity without needing to manually verify the external access token? I believe that’s the purpose, however I cannot get it working. I send a valid external access token from google and it gets rejected with a 401.
I have this line in Startup.Auth btw:
app.UseOAuthBearerTokens(new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(),
AuthorizeEndpointPath = new PathString("/AccountApi/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
});
Alternatively, it is possible to use "/Token" endpoint to trade an external access token for a local one? Which approach is correct?
Studying the implementation by Taiseer Joudeh
the /ExternalLogin endpoint replaces the OWIN Authentication Challenge.
The AngularJS LoginController makes a call to the authService.obtainAccessToken when an externally authenticated user has not been found in Identity Provider:
if (fragment.haslocalaccount == 'False') {
...
}
else {
//Obtain access token and redirect to orders
var externalData = { provider: fragment.provider,
externalAccessToken: fragment.external_access_token };
authService.obtainAccessToken(externalData).then(function (response) {
$location.path('/orders');
It uses the VerifyExternalAccessToken to perform a reverse lookup against Google and Facebook API's to get claim info for the bearer token.
if (provider == "Facebook")
{
var appToken = "xxxxxx";
verifyTokenEndPoint = string.Format("https://graph.facebook.com/debug_token?input_token={0}&access_token={1}", accessToken, appToken);
}
else if (provider == "Google")
{
verifyTokenEndPoint = string.Format("https://www.googleapis.com/oauth2/v1/tokeninfo?access_token={0}", accessToken);
}
else
{
return null;
}
If token is found, it returns a new ASP.NET bearer token
var accessTokenResponse = GenerateLocalAccessTokenResponse(user.UserName);
return Ok(accessTokenResponse);
With [HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)] the OWIN Middleware uses the external bearer token to access the 3rd party's Cookie and Register a new account (Or find existing).
OWIN Middleware cannot be configured to accept external bearer token instead of local authority tokens. External bearer tokens are only used for Authentication and Registration.

Categories