var claims = new[]
{
new Claim("UserID", user.ID.ToString()),
new Claim(ClaimTypes.Role, "pioneer")
};
var principal = new ClaimsPrincipal(
new ClaimsIdentity(
claims, CookieAuthenticationDefaults.AuthenticationScheme));
await HttpContext.Authentication.SignInAsync("Cookies", principal);
This is the cookie that I'm creating and using to sign in as per instruction from this tutorial: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie
The cookie is being created, but I don't know how to access the cookie or the data inside of it to get the user.ID value.
Once you sign via HttpContext.Authentication.SignInAsync, you should be able to access the User property in your controllers (it is part of ControllerBase), or HttpContext.User.
This User is a ClaimsPrincipal object that is created from the cookie automatically.
Related
I'm allowing users logged in an external application to jump into our application with their access token through Keycloak's identity brokering and external to internal token exchange.
Now I'd like to establish an SSO session in an embedded JxBrowser in our application similar to a regular browser login flow, where three cookies are set in the browser: AUTH_SESSION, KEYCLOAK_SESSION(_LEGACY) and KEYCLOAK_IDENTITY(_LEGACY).
KEYCLOAK_IDENTITY contains a token of type Serialized-ID which looks somewhat similar to an ID token.
Is it possible to create the KEYCLOAK_IDENTITY cookie using the exchanged (internal) access and/or ID token and, provided that the other two cookies are correctly created as well, would this establish a valid SSO session?
Basically all I am missing is how I could obtain or create the Serialized-ID type token.
One way to achieve this:
Implement a custom endpoint following this example
Note that the provider works fine for me without registering it in standalone.xml, I'm just adding the JAR to the Keycloak Docker image.
Add a method that validates a given access token, looks up the user, gets the user session and sets the cookies in the response (most error handling omitted for brevity):
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("sso")
public Response sso(#Context final HttpRequest request) {
final HttpHeaders headers = request.getHttpHeaders();
final String authorization = headers.getHeaderString(HttpHeaders.AUTHORIZATION);
final String[] value = authorization.split(" ");
final String accessToken = value[1];
final AccessToken token = Tokens.getAccessToken(accessToken, keycloakSession);
if (token == null) {
throw new ErrorResponseException(Errors.INVALID_TOKEN, "Invalid access token", Status.UNAUTHORIZED);
}
final RealmModel realm = keycloakSession.getContext().getRealm();
final UriInfo uriInfo = keycloakSession.getContext().getUri();
final ClientConnection clientConnection = keycloakSession.getContext().getConnection();
final UserModel user = keycloakSession.users().getUserById(token.getSubject(), realm);
final UserSessionModel userSession = keycloakSession.sessions().getUserSession(realm, token.getSessionState());
AuthenticationManager.createLoginCookie(keycloakSession, realm, user, userSession, uriInfo, clientConnection);
return Response.noContent().build();
}
Disclaimer: I am not completely certain this implementation does not imply any security issues, but since Tokens.getAccessToken(accessToken, keycloakSession) does full validation of the access token, setting the cookies should only be possible with a valid access token.
For CORS, add:
#OPTIONS
#Produces(MediaType.APPLICATION_JSON)
#Path("sso")
public Response preflight(#Context final HttpRequest request) {
return Cors.add(request, Response.ok("", MediaType.APPLICATION_JSON))
.auth()
.preflight()
.allowedMethods("GET", "OPTIONS")
.build();
}
and in sso():
return Cors.add(request, Response.ok("", MediaType.APPLICATION_JSON))
.auth()
.allowedMethods("GET")
.allowedOrigins(token)
.build();
What I am uncertain about is why Firefox preflights the GET request, making it necessary to handle that.
I want to authenticate AAD users to access powerBi resources through MSAL by using application ID and secret. So i want to get the access token and cache it in SQL Db.
went through the documentation but it explains the scenario of using MSAL for sign-in.
also went through the tutorial
i was able to to do the necessary implementations to get the token.
how can i get the access token and cache it, in a scenario like this?
As indicated in other answers, caching tokens are useful in case when you have users signing in, as once the access token expires (typically after 1 hour), you don't want to keep prompting the users to re-authenticate.
So help with these scenarios, Azure AD issues a refresh token along with an access token that is used to fetch access tokens once they expire. Caching is required to cache these refresh tokens as they are valid for 90 days.
When an app signs as itself (and not signing in a user), the client credentials flow is used and it only needs the app id (clientId) and the credential (secret/certificate) to issue an access token. The MSAL library will automatically detect when the access token expires and will use the clientId/credential combination to automatically get a new access token. So caching is not necessary.
The sample you should be looking at is this one.
I'n not sure to understand, I hope these few lines of code will help you.
First, customize token cache serialization :
public class ClientApplicationBuilder
{
public static IConfidentialClientApplication Build()
{
IConfidentialClientApplication clientApplication =
ConfidentialClientApplicationBuilder
.Create(ClientId)
.WithRedirectUri(RedirectUri)
.WithClientSecret(ClientSecret)
.Build();
clientApplication.UserTokenCache.SetBeforeAccessAsync(BeforeAccessNotification);
clientApplication.UserTokenCache.SetAfterAccessAsync(AfterAccessNotification);
return clientApplication;
}
private static async Task<byte[]> GetMsalV3StateAsync()
{
//TODO: Implement code to retrieve MsalV3 state from DB
}
private static async Task StoreMsalV3StateAsync(byte[] msalV3State)
{
//TODO: Implement code to persist MsalV3 state to DB
}
private static async Task BeforeAccessNotification(TokenCacheNotificationArgs args)
{
byte[] msalV3State = await GetMsalV3StateAsync();
args.TokenCache.DeserializeMsalV3(msalV3State);
}
private static async Task AfterAccessNotification(TokenCacheNotificationArgs args)
{
if (args.HasStateChanged)
{
byte[] msalV3State = args.TokenCache.SerializeMsalV3();
await StoreMsalV3StateAsync(msalV3State);
}
}
}
Here's an example to acquire token (by Authorization Code) :
public class MsAccountController
: Controller
{
private readonly IConfidentialClientApplication _clientApplication;
public MsAccountController()
{
_clientApplication = ClientApplicationBuilder.Build();
}
[HttpGet]
public async Task<IActionResult> Index()
{
Uri authorizationRequestUrl = await _clientApplication.GetAuthorizationRequestUrl(ClientApplicationHelper.Scopes).ExecuteAsync();
string authorizationRequestUrlStr = authorizationRequestUrl.ToString();
return Redirect(authorizationRequestUrlStr);
}
[HttpGet]
public async Task<IActionResult> OAuth2Callback(string code, string state)
{
AuthenticationResult authenticationResult = await _clientApplication.AcquireTokenByAuthorizationCode(scopes, code).ExecuteAsync();
return Ok(authenticationResult);
}
}
Finally, acquire a token silently and use auth result for your API client :
public class TaskController
: Controller
{
private readonly IConfidentialClientApplication _clientApplication;
public TaskController()
{
_clientApplication = ClientApplicationBuilder.Build();
}
[HttpGet]
public async Task<IActionResult> Index()
{
IEnumerable<IAccount> accounts = await _clientApplication.GetAccountsAsync();
AuthenticationResult result = await _clientApplication.AcquireTokenSilent(ClientApplicationHelper.Scopes, accounts.FirstOrDefault()).ExecuteAsync();
//TODO: Create your API client using authentication result
}
}
Regards
You can cache the access token (actually, the library does this already), but it is valid for 1 hour only. So it makes no sense to save it in a database, because it will expire quickly.
You should cache the credentials needed to obtain the token (user name and password, app ID and secret, or certificate) and obtain a token when needed.
I've done this for a confidential client application, where I connected to O365 in order to send email.
First, register your app in azure app as the docs say.
Then, set up your confidential client application and use as singleton.
var app = ConfidentialClientApplicationBuilder.Create(clientId)
.WithClientSecret(clientSecret)
.WithRedirectUri(redirectUri)
.WithLegacyCacheCompatibility(false)
.WithAuthority(AadAuthorityAudience.AzureAdAndPersonalMicrosoftAccount)
.Build();
app.AddDistributedTokenCache(services => {
services.AddDistributedTokenCaches();
services.AddDistributedSqlServerCache(options => {
options.SchemaName = "dbo";
options.TableName = "O365TokenCache";
options.ConnectionString = sqlCacheConnectionString;
options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);
});
});
services.AddSingleton<IConfidentialClientApplication>(app);
The first time you connect a user, you need to redirect to Microsoft identity. You can create the URL using:
var authUrl = await app.GetAuthorizationRequestUrl(new[] { "email", "offline_access", "https://outlook.office.com/SMTP.Send" }).ExecuteAsync();
(Check your scopes are what you want)
When they come back to your redirect url you then get the code from query string and acquire the refresh token:
var token = await app.AcquireTokenByAuthorizationCode(scopes, code).ExecuteAsync();
When you do this, MSAL will cache the access token and refresh token for you, but here's the thing they don't mention: you have to create the table in SQL yourself! If you don't, it just silently fails.
dotnet tool install -g dotnet-sql-cache
dotnet sql-cache create "<connection string>" dbo O365TokenCache
Once you have the access token the first time you can use the following later
var account = await app.GetAccountAsync(accountId);
var token = await app.AcquireTokenSilent(scopes, account).ExecuteAsync();
When you get the access token the first time, you need to look at token.Account.HomeAccountId.Identifier as this is the ID that you need when you call GetAccountAsync. For some reason, GetAccountsAsync (note the extra "s") always returns empty for me but passing the correct ID to GetAccountAsync does return the right one.
For me, I simply store that ID against the logged in user so that I can get that ID at a later time.
Am trying to write custom middleware in the ASP.net core pipeline, as part of my invoke, would like to append/add cookie, so then next middleware in the pipeline can access those cookie.
getting compiling error on set the cookie value. Can anyone recommend work around for this.
Note: When I tried with Response.Cookie , it works but only problem is, cookie is reflecting only on next request from the browser, but I need this to be reflecting on the next middleware in the pipeline immediately after execute this.
below code snippet
public async Task Invoke(HttpContext httpContext)
{
var queryParameters = httpContext.Request.Query;
var cookies = httpContext.Request.Cookies;
if (!cookies.ContainsKey(".AspNetCore.Session")
|| cookies[".AspNetCore.Session"] != "new_key")
{
httpContext.Request.Cookies[".AspNetCore.Session"] = "new_key";
}
await _next.Invoke(httpContext);
}
You cannot use cookie's value in same request. However, you could use good old HttpContext.Items.
public async Task InvokeAsync(HttpContext context)
{
context.Request.HttpContext.Items["key"] = "Hello!";
await _next(context);
}
You then retrieve it as
var value = HttpContext.Items["key"];
In my case I have an AuthorizationHandler that performs some checks to determine the user details and whether the user is logged in. The auth handler stores some of this info in a token in the request headers, so it can be easily accessed by the controllers.
When the user is logged in, this token can be read from the HttpContext.Request.Headers in a standard controller and all is well.
When the user is not logged in, the auth handler returns failure and so the request is redirected to "/login". Sadly the token header is not preserved across the redirect, so in my LoginController the token is null.
The only way I could make the token available to both a standard controller and LoginController is to store the token in both the request headers AND response cookies. This cookie can be read from the LoginController in the HttpContext.Request.Cookies collection. I set it to be short-lived as it's only needed briefly (it'll disappear after 5 seconds)
Here is part of the code from my auth handler:
HttpRequest request = _httpContextAccessor.HttpContext.Request;
HttpResponse response = _httpContextAccessor.HttpContext.Response;
request.Headers["X-Token"] = encryptedToken;
response.Cookies.Append("TokenCookie", encryptedToken, new CookieOptions
{
MaxAge = TimeSpan.FromSeconds(5),
Secure = true,
IsEssential = true,
});
We migrated a multitenant MVC application from ASP.NET Membership Provider to ASP.NET Identity.
This is my Startup.Auth.cs (simplified):
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity =
SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, Identity, int>(
TimeSpan.FromMinutes(30),
(manager, user) =>
manager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie),
clIdentity => clIdentity.GetUserId<int>())
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
}
In our multitenant application, each tenant has its own 'slug' (e.g. http://example.com/tenant1/ and http://example.com/tenant2/)
However, currently, the cookies are stored in the root. This causes security issues as users from tenant1 are automatically logged in on the website from tenant2.
How can we make the CookiePath (in CookieAuthenticationOptions) variable so that it changes depending on the tenant?
I fixed this issue with a lot of help from dampee.
The CookiePath in the CookieAuthenticationOptions object is evaluated only once: at application startup.
The easiest solution (workaround) was to create a derived CookieAuthenticationProvider that overrides ResponseSignIn and ResponseSignOut.
They both have an argument called context which has a property called CookiePath. Modify this property in both of these methods to change the CookiePath.
You can also use the class I created.
Then all you have to do is replace the CookieAuthenticationProvider in the CookieAuthenticationOptions with the one you just created.
This works for the ApplicationCookie. The ExternalSignInCookie doesn't matter that much since it is used only temporarily while signing in with an external login.
Improving on SamuelDebruyn's own solution, I found you can pass the path from the SignIn call to the provider using an AuthenticationProperties object. This way, instead of extracting the path from the request context as his gist shows, you can pass it explicitly from the source:
// method inside web api controller
private void SignIn(string name, string cookiePath)
{
var claims = new[] { new Claim(ClaimTypes.Name, name) };
var identity = new ClaimsIdentity(claims, "ApplicationCookie");
var options = new AuthenticationProperties();
options.Dictionary["CustomCookiePath"] = cookiePath;
var authManager = Request.GetOwinContext().Authentication;
authManager.SignIn(options, identity);
}
// Startup.cs
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
Provider = new CustomCookieProvider()
});
// custom provider
public class CustomCookieProvider : CookieAuthenticationProvider
{
public override void ResponseSignIn(CookieResponseSignInContext context)
{
context.CookieOptions.Path = context.Properties.Dictionary["CustomCookiePath"];
base.ResponseSignIn(context);
}
}
You can use a custom ICookieManager to dynamically return the cookie value to the CookieAuthenticationProvider based on whatever is in the request, to do this you would still maintain the CookiePath as "/" and then leave it up to the ICookieManager to return (or write) the cookie however you want. The CookieManager is an option on the CookieAuthenticationOptions. I blogged about this here: http://shazwazza.com/post/owin-cookie-authentication-with-variable-cookie-paths/
The situation:
I have a website and creates "posts". As a new post is created I want to send the new post to my facebook pages feed. I have all of the code down to do this and it works fine as long as I get an access token from the graph API explorer tool. This is not going to work as it expires after about an hour. When I generate the access token from code, it appears that it is a app access token and it does not give me access to my page. So the big question is how do I obtain a user access token from code that will have access to post to my page.
Here is how I am getting the access token.
private static string GetApiAccessToken()
{
var client = new FacebookClient();
dynamic result = client.Get("oauth/access_token", new
{
client_id = SessionGetter.Instance.FacebookApiKey,
client_secret = SessionGetter.Instance.FacebookSecretKey,
grant_type = "client_credentials",
scope = "manage_pages"
});
return result.access_token;
}
Then I use the access token to try and get the Page access token and this is where it tells me that I don't have authorization and all I get back in the dictionary is an "id".
private static string GetPageAccessToken(string accessToken, string pageId)
{
try
{
var fb = new FacebookClient(accessToken);
var parameters = new Dictionary<string, object>();
parameters["fields"] = "access_token";
var result = (IDictionary<string, object>)fb.Get(pageId, parameters);
var pageAccessToken = (string)result["access_token"];
return pageAccessToken;
}
catch (FacebookApiException ex)
{
}
return null;
}
Now like I said, if I use the access token from the graph explorer, the code works fine.
Then the post is made to the graph API
var facebookClient = new FacebookClient(pageAccessToken);
var result = (IDictionary<string, object>)facebookClient.Post("me/feed", new Dictionary<string, object>
{{"message",postMessage}, {"picture", csLogo},
{"link", LinkHelper.AssignmentUrl(wrapper.Assignment)}});
Make sure the user access token has manage_pages extended permissions.
then make a request to me/accounts to the get the page access token.
Then post to {pageid}/feed using the page access token.