We need to be able to support both Basic Authentication and Bearer Authentication. Bearer Authentication being the preferred / default method. Basic is for some clients that cannot support Bearer Authentication.
We want to be able to manage privileges from a since source. The idea is that if a user is using Basic Auth, that user would actually send us his clientId/clientSecret and we will get the token for him.
In order to keep this hole as closed as possible, clientId needs to be validated versus a whitelist first. How can I do that without have to completely re-implement the Bearer Authentication. Even if we whitelisted the clientId we still want to validate the token exactly like if they would have sent it.
public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public const string AuthTypeName = "Basic ";
private const string _authHeaderName = "Authorization";
public BasicAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var authHeader = AuthenticationHeaderValue.Parse(Request.Headers[_authHeaderName]);
var (clientId, clientSecret) = DecodeUserIdAndPassword(authHeader.Parameter);
if (!clientIdIsWhiteListed.IsSatisfiedBy(clientId))
return AuthenticateResult.Fail("Invalid Credentials");
IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create(clientId)
.WithTenantId(tenantId)
.WithClientSecret(clientSecret)
.Build();
string[] scopes = new string[] { "MyScope" };
var result = await app.AcquireTokenForClient(scopes)
.ExecuteAsync();
// Here is where I have no clue how to continue
Request.Headers[_authHeaderName] = $"Bearer {result.AccessToken}"; //?
return AuthenticateResult.NoResult(); //?
}
}
I was so close to the correct answer, well here is what I did and it works.
1 line change
public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public const string AuthTypeName = "Basic ";
private const string _authHeaderName = "Authorization";
public BasicAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var authHeader = AuthenticationHeaderValue.Parse(Request.Headers[_authHeaderName]);
var (clientId, clientSecret) = DecodeUserIdAndPassword(authHeader.Parameter);
if (!clientIdIsWhiteListed.IsSatisfiedBy(clientId))
return AuthenticateResult.Fail("Invalid Credentials");
IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create(clientId)
.WithTenantId(tenantId)
.WithClientSecret(clientSecret)
.Build();
string[] scopes = new string[] { "MyScope" };
var result = await app.AcquireTokenForClient(scopes)
.ExecuteAsync();
Request.Headers[_authHeaderName] = $"Bearer {result.AccessToken}";
return await Context.AuthenticateAsync("Bearer");
}
}
Related
I have built a Blazor Server App with Azure AD authentication. This server app access a web api written in net core and sends the JWT token to that api. Everything is working, data is gathered, page is displayed accordingly.
The problem is: after some time, when user interacts with some menu option in UI, nothing else is returned from webapi. After some tests I found out that the token has expired, then when it is sent to web api, it is not working. But the AuthenticationState remains same, like it is authenticated and valid irrespective the token is expired.
Thus, I have been trying some suggestions like : Client side Blazor authentication token expired on server side. Actually it is the closest solution I got.
But the problem is that, after implemented a CustomAuthenticationStateProvider class, even after injected it, the default AuthenticationStateProvider of the app remains like ServerAuthenticationStateProvider and not the CustomAuthenticationStateProvider I have implemented. This is part of my code:
public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly IConfiguration _configuration;
private readonly ITokenAcquisition _tokenAcquisition;
public CustomAuthenticationStateProvider(IConfiguration configuration, ITokenAcquisition tokenAcquisition)
{
_configuration = configuration;
_tokenAcquisition = tokenAcquisition;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var apiScope = _configuration["DownloadApiStream:Scope"];
var anonymousState = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
string savedToken = string.Empty;
try
{
savedToken = await _tokenAcquisition.GetAccessTokenForUserAsync(new[] { apiScope });
}
catch (MsalUiRequiredException)
{
savedToken = string.Empty;
}
catch (Exception)
{
savedToken = string.Empty;
}
if (string.IsNullOrWhiteSpace(savedToken))
{
return anonymousState;
}
var claims = ParseClaimsFromJwt(savedToken).ToList();
var expiry = claims.Where(claim => claim.Type.Equals("exp")).FirstOrDefault();
if (expiry == null)
return anonymousState;
// The exp field is in Unix time
var datetime = DateTimeOffset.FromUnixTimeSeconds(long.Parse(expiry.Value));
if (datetime.UtcDateTime <= DateTime.UtcNow)
return anonymousState;
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(claims, "jwt")));
}
public void NotifyExpiredToken()
{
var anonymousUser = new ClaimsPrincipal(new ClaimsIdentity());
var authState = Task.FromResult(new AuthenticationState(anonymousUser));
NotifyAuthenticationStateChanged(authState);
}
private IEnumerable<Claim> ParseClaimsFromJwt(string jwt)
{
var claims = new List<Claim>();
var payload = jwt.Split('.')[1];
var jsonBytes = ParseBase64WithoutPadding(payload);
var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);
keyValuePairs.TryGetValue(ClaimTypes.Role, out object roles);
if (roles != null)
{
if (roles.ToString().Trim().StartsWith("["))
{
var parsedRoles = JsonSerializer.Deserialize<string[]>(roles.ToString());
foreach (var parsedRole in parsedRoles)
{
claims.Add(new Claim(ClaimTypes.Role, parsedRole));
}
}
else
{
claims.Add(new Claim(ClaimTypes.Role, roles.ToString()));
}
keyValuePairs.Remove(ClaimTypes.Role);
}
claims.AddRange(keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString())));
return claims;
}
private byte[] ParseBase64WithoutPadding(string base64)
{
switch (base64.Length % 4)
{
case 2: base64 += "=="; break;
case 3: base64 += "="; break;
}
return Convert.FromBase64String(base64);
}
}
This is my Program.cs where I added the services :
builder.Services.AddScoped<CustomAuthenticationStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(provider => provider.GetRequiredService<CustomAuthenticationStateProvider>());
Here in the MainLayou.razor, I inject the service and try to use it :
#inject CustomAuthenticationStateProvider authenticationStateProvider;
protected async override Task OnInitializedAsync()
{
var authState = await authenticationStateProvider.GetAuthenticationStateAsync();
if (authState.User?.Identity == null || !authState.User.Identity.IsAuthenticated)
{
authenticationStateProvider.NotifyExpiredToken();
}
await base.OnInitializedAsync();
}
The problem comes up here, because the authenticationStateProvider is not an instance of the CustomAuthenticationStateProvider , but the instance of ServerAuthenticationStateProvider. It is like AuthenticationStateProvider was not replaced by the custom implementation, therefore I can't use the NotifyAuthenticationStateChanged and inform the CascadingAuthenticationState that it was changed.
If anyone has already been thru this or have any suggestion, it would be appreciated.
Actually I just wanna to change authentication state to not authenticated. So user will be pushed to login again using Azure AD.
Thanks
I have an Azure Function App with Azure Active Directory configured but when I call if from my client I keep getting an Unauthorized response.
I have tried a couple different scenarios but nothing worked. Below is a snippet of the last bit of code that I tried.
///
var #params2 = new NameValueCollection
{
{"grant_type", "client_credentials"},
{"client_id", $"{ClientId}"},
{"client_secret", $"{ClientSecret}"},
{"username", userId},
{"resource", "https://management.azure.com/"}
};
var queryString2 = HttpUtility.ParseQueryString(string.Empty);
queryString2.Add(#params2);
var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
{"grant_type", "client_credentials"},
{"client_id", ClientId},
{"client_secret", ClientSecret},
{"username", userId}
});
var authorityUri2 = $"{string.Format(CultureInfo.InvariantCulture, AadInstance, Tenant).TrimEnd('/')}/oauth2/token";
//var authorityUri2 = $"https://login.microsoftonline.com/{Tenant}/v2.0/.well-known/openid-configuration";
var authUri2 = String.Format("{0}?{1}", authorityUri2, queryString2);
var client2 = new HttpClient();
var message = client2.PostAsync(authorityUri2, content).Result;
//var message = client2.GetAsync(authorityUri2).Result;
var response = message.Content.ReadAsStringAsync().Result;
dynamic values=null;
try
{
values = JsonConvert.DeserializeObject<Dictionary<string, string>>(response);
}
catch
{
values = response;
}
var AuthToken2 = values["access_token"];
client2.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AuthToken2);
HttpResponseMessage response2 = await client2.GetAsync(AppBaseAddress.TrimEnd('/') + "/api/AADIntegration");
if (response.IsSuccessStatusCode)
{
// Read the response and data-bind to the GridView to display To Do items.
string s = await response.Content.ReadAsStringAsync();
log.LogInformation($"Success while getting / api / AADIntegration : {s}");
return (ActionResult)new OkObjectResult(s);
}
else
{
string failureDescription = await response.Content.ReadAsStringAsync();
log.LogInformation($"An error occurred while getting / api / AADIntegration : {response.ReasonPhrase}\n {failureDescription}");
return (ActionResult)new OkObjectResult(failureDescription);
}
Data should returned from the Function App.
For client_credentials grant flow your code seems little different. Here I am giving you exact sample for azure function. Just plug and play :))
Example contains:
How would you get token using client_credentials flow
Getting user list From Azure Active Directory tenant using above
token
Access Token Class:
public class AccessTokenClass
{
public string token_type { get; set; }
public string expires_in { get; set; }
public string resource { get; set; }
public string scope { get; set; }
public string access_token { get; set; }
}
Reference To Add:
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Net.Http;
using System.Collections.Generic;
using System.Net.Http.Headers;
Azure Function Body:
public static class FunctionGetUserList
{
[FunctionName("FunctionGetUserList")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
try
{
log.LogInformation("C# HTTP trigger function processed a request.");
//Token Request endpoint Just replace yourTennantId/Name
string tokenUrl = $"https://login.microsoftonline.com/yourTennantId/Name.onmicrosoft.com/oauth2/token";
var tokenRequest = new HttpRequestMessage(HttpMethod.Post, tokenUrl);
tokenRequest.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
["client_id"] = "b603c7bead87-Your_client_id-e6921e61f925",
["client_secret"] = "Vxf1SluKbgu4P-Your_client_Secret-F0Nf3wE5oGl/2XDSeZ=",
["resource"] = "https://graph.microsoft.com"
});
dynamic json;
AccessTokenClass results = new AccessTokenClass();
HttpClient client = new HttpClient();
var tokenResponse = await client.SendAsync(tokenRequest);
json = await tokenResponse.Content.ReadAsStringAsync();
results = JsonConvert.DeserializeObject<AccessTokenClass>(json);
var accessToken = results.access_token;
//Create Request To Server
using (HttpClient clientNew = new HttpClient())
{
//Pass Token on header
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//Get Data from API
var requestToAzureEndpoint = await client.GetAsync("https://graph.microsoft.com/v1.0/users");
if (requestToAzureEndpoint.IsSuccessStatusCode)
{
var result_string = await requestToAzureEndpoint.Content.ReadAsStringAsync();
dynamic responseResults = JsonConvert.DeserializeObject<dynamic>(result_string);
return new OkObjectResult(responseResults);
}
else
{
var result_string = await requestToAzureEndpoint.Content.ReadAsStringAsync();
return new OkObjectResult(result_string);
}
}
}
catch (Exception ex)
{
return new OkObjectResult(ex.Message);
}
}
}
Point To Remember
For Azure Active Directory List users access make sure you have following permission:
User.Read.All
Permission Type: Application
You can check here. See the screen shot for better understanding; make sure you have clicked "Grant admin consent for yourTenant" after adding permission.
Note: This is how you can access Azure Active Directory Token using Azure Function after that how to access resource using that token to a specific API endpoint efficiently.
Are you sure you have properly implemented this properly? It looks like a few of your parameters are wrong for the client credential flow. Please double check that you are properly following the client credential flow.
The client credential grant flow is documented here : https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
But for more information on getting this properly working in your function app, please refer to the blog below for more information/help on implementing this.
https://blogs.msdn.microsoft.com/ben/2018/11/07/client-app-calling-azure-function-with-aad/
The value of resource is not correct.
Replace {"resource", "https://management.azure.com/"} with {"resource", $"{ClientId}"}
I'm using the azure mobile services sdk to do offline sync. I made my api so that it is protected with basic authentication using email and password.
How can I embed these credentials with the MobileServiceClient, so that whenever I call a method it has the correct auth credentials.
this is my existing code for the MobileServiceClient.
var handler = new AuthHandler();
//TODO 1: Create our client
//Create our client
MobileService = new MobileServiceClient(Helpers.Keys.AzureServiceUrl, handler)
{
SerializerSettings = new MobileServiceJsonSerializerSettings()
{
CamelCasePropertyNames = true
}
};
//assign mobile client to handler
handler.Client = MobileService;
MobileService.CurrentUser = new MobileServiceUser(Settings.UserId);
MobileService.CurrentUser.MobileServiceAuthenticationToken = Settings.AuthToken;
AuthHandler Class
class AuthHandler : DelegatingHandler
{
public IMobileServiceClient Client { get; set; }
private static readonly SemaphoreSlim semaphore = new SemaphoreSlim(1);
private static bool isReauthenticating = false;
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
//Clone the request in case we need to send it again
var clonedRequest = await CloneRequest(request);
var response = await base.SendAsync(clonedRequest, cancellationToken);
//If the token is expired or is invalid, then we need to either refresh the token or prompt the user to log back in
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
if (isReauthenticating)
return response;
var service = DependencyService.Get<AzureService>();
var client = new MobileServiceClient(Helpers.Keys.AzureServiceUrl, null);
client.CurrentUser = new MobileServiceUser(Settings.UserId);
client.CurrentUser.MobileServiceAuthenticationToken = Settings.AuthToken;
string authToken = client.CurrentUser.MobileServiceAuthenticationToken;
await semaphore.WaitAsync();
//In case two threads enter this method at the same time, only one should do the refresh (or re-login), the other should just resend the request with an updated header.
if (authToken != client.CurrentUser.MobileServiceAuthenticationToken) // token was already renewed
{
semaphore.Release();
return await ResendRequest(client, request, cancellationToken);
}
isReauthenticating = true;
bool gotNewToken = false;
try
{
gotNewToken = await RefreshToken(client);
//Otherwise if refreshing the token failed or Facebook\Twitter is being used, prompt the user to log back in via the login screen
if (!gotNewToken)
{
gotNewToken = await service.LoginAsync();
}
}
catch (System.Exception e)
{
Debug.WriteLine("Unable to refresh token: " + e);
}
finally
{
isReauthenticating = false;
semaphore.Release();
}
if (gotNewToken)
{
if (!request.RequestUri.OriginalString.Contains("/.auth/me")) //do not resend in this case since we're not using the return value of auth/me
{
//Resend the request since the user has successfully logged in and return the response
return await ResendRequest(client, request, cancellationToken);
}
}
}
return response;
}
private async Task<HttpResponseMessage> ResendRequest(IMobileServiceClient client, HttpRequestMessage request, CancellationToken cancellationToken)
{
// Clone the request
var clonedRequest = await CloneRequest(request);
// Set the authentication header
clonedRequest.Headers.Remove("X-ZUMO-AUTH");
clonedRequest.Headers.Add("X-ZUMO-AUTH", client.CurrentUser.MobileServiceAuthenticationToken);
// Resend the request
return await base.SendAsync(clonedRequest, cancellationToken);
}
private async Task<bool> RefreshToken(IMobileServiceClient client)
{
var authentication = DependencyService.Get<IAuthentication>();
if (authentication == null)
{
throw new InvalidOperationException("Make sure the ServiceLocator has an instance of IAuthentication");
}
try
{
return await authentication.RefreshUser(client);
}
catch (System.Exception e)
{
Debug.WriteLine("Unable to refresh user: " + e);
}
return false;
}
private async Task<HttpRequestMessage> CloneRequest(HttpRequestMessage request)
{
var result = new HttpRequestMessage(request.Method, request.RequestUri);
foreach (var header in request.Headers)
{
result.Headers.Add(header.Key, header.Value);
}
if (request.Content != null && request.Content.Headers.ContentType != null)
{
var requestBody = await request.Content.ReadAsStringAsync();
var mediaType = request.Content.Headers.ContentType.MediaType;
result.Content = new StringContent(requestBody, Encoding.UTF8, mediaType);
foreach (var header in request.Content.Headers)
{
if (!header.Key.Equals("Content-Type", StringComparison.OrdinalIgnoreCase))
{
result.Content.Headers.Add(header.Key, header.Value);
}
}
}
return result;
}
}
How can I embed these credentials with the MobileServiceClient, so that whenever I call a method it has the correct auth credentials.
Per my understanding, the AuthHandler class could provide a method for setting the current valid user info after the user has successfully logged in with the correct email and password. Also, you need to cache the AuthHandler instance which is used to construct the MobileServiceClient instance, after user logged, you could embed the current user info into the AuthHandler instance.
If you are talking about providing a sign-in process with a username and password rather than using a social provider, you could just follow Custom Authentication for building your CustomAuthController to work with App Service Authentication / Authorization (EasyAuth). For your client, you could use the following code for logging:
MobileServiceUser azureUser = await _client.LoginAsync("custom", JObject.FromObject(account));
Moreover, you need to cache the MobileServiceAuthenticationToken issued by your mobile app backend and manually valid the cached token and check the exp property of the JWT token under the SendAsync method of your AuthHandler class, and explicitly call LoginAsync with the cached user account for acquiring the new MobileServiceAuthenticationToken when the current token would be expired soon or has expired without asking the user to log in again. Detailed code sample, you could follow adrian hall's book about Caching Tokens.
Or if you are talking about Basic access authentication, you could also refer the previous part about embedding credentials into your AuthHandler. For your server-side, you could also add your custom DelegatingHandler to validate the authorization header and set the related Principal to HttpContext.Current.User. And you could initialize your DelegatingHandler under Startup.MobileApp.cs file as follows:
HttpConfiguration config = new HttpConfiguration();
config.MessageHandlers.Add(new MessageHandlerBasicAuthentication());
Moreover, you could follow Basic Authentication Using Message Handlers In Web API.
I'm a servicestack newbie. I'm trying to figure out how to send custom parameter on authentication.
As far as I understood, that's the step to authenticate a client and than execute a set of call within a session
var jsonClient = new JsonServiceClient("http://localhost:55679/");
var authResponse = client.Send(new Authenticate
{
provider = "myProvider",
UserName = "user",
Password = "pwd",
RememberMe = true,
});
var jResponse = jsonClient.Get<CountriesResponse>(request);
Console.WriteLine(jResponse.Countries.Count);
So far so good, I configurated my apphost as following and everything works as expected.
Plugins.Add(new AuthFeature(() => new CustomUserSession(), new IAuthProvider[] {
new MyAuthProvider(),
}));
What should I do if, instead of sending ServiceStack.Authenticate, I'd like to send my MyAuthenticate
request that has same custom properties, somenthing like this?
var authResponse = client.Send(new MyAuthenticate
{
provider = "myProvider",
UserName = "user",
Password = "pwd",
RememberMe = true,
AppId = "AppId",
ProjectId = "ProjectId"
});
My goal is to send custom parameter while I'm authenticating the user, not just those allowed by Authenticate built-in request, and than store those extra parameter within my CustomUserSession.
Thanks
Sending additional info on QueryString or HttpHeaders
As you can't change the built-in Authenticate Request DTO, one way to send additional metadata is to add extra info on the QueryString or HTTP Headers.
If you wanted to use the .NET Service Clients to do this you would need to use the RequestFilter, e.g:
var client = new JsonServiceClient(BaseUrl) {
RequestFilter = req => {
req.QueryString["AppId"] = appId;
req.QueryString["ProjectId"] = appId;
}
};
var authResponse = client.Send(new Authenticate { ... });
Otherwise creating custom Request is often more flexible using ServiceStack's built-in HTTP Utils, e.g:
var url = "{0}/auth/myProvider".Fmt(BaseUrl)
.AddQueryParam("AppId", appId)
.AddQueryParam("ProjectId", projectId);
var authResponse = url.PostJsonToUrl(new Authenticate { ... });
On the server the additional data will be available in the QueryString of the current request which you can get from IServiceBase or IRequest args, e.g:
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
...
public override IHttpResult OnAuthenticated(IServiceBase authService,
IAuthSession session, IAuthTokens tokens, Dictionary<string, string> authInfo)
{
...
var customSession = (CustomUserSession)session;
customSession.AppId = authService.Request.QueryString["AppId"];
customSession.ProjectId = authService.Request.QueryString["ProjectId"];
return base.OnAuthenticated(authService, session, tokens, authInfo);
}
}
Custom Meta dictionary now available on Authenticate Request DTO
To make this use-case a little easier a new Dictionary<string,string> Meta property was added on the Authenticate DTO which makes calling from the Typed Service Clients a little nicer since you don't have to use a filter, e.g:
var client = new JsonServiceClient(BaseUrl);
var authResponse = client.Send(new Authenticate {
...
Meta = new Dictionary<string, string> { {"AppId", appId}, {"ProjectId", pId} },
});
Which you can access from the Authenticate DTO directly, e.g:
var authRequest = (Authenticate)authService.Request.Dto;
customSession.AppId = authRequest.Meta["AppId"];
customSession.ProjectId = authRequest.Meta["ProjectId"];
The new Meta property is available from v4.0.35+ that's currently available on MyGet.
Use your own Custom Authentication Service
A more disruptive alternative approach to be able to use your own MyAuthenticate DTO is to handle the authentication request in your own Service and then delegate to the AuthService, e.g:
public class MyAuthenticate : Authenticate
{
public string AppId { get; set; }
public string ProjectId { get; set; }
}
public class MyAuthServices : Service
{
public object Any(MyAuthenticate request)
{
using (var auth = base.ResolveService<AuthenticateService>())
{
var response = auth.Post(request);
var authResponse = response as AuthenticateResponse;
if (authResponse != null) {
var session = base.SessionAs<CustomUserSession>();
session.AppId = request.AppId;
session.ProjectId = request.ProjectId;
this.SaveSession(session);
}
return response;
}
}
}
I am attempting to authenticate against MVC and ServiceStack following the example here - https://github.com/ServiceStack/ServiceStack.UseCases/tree/master/CustomAuthenticationMvc.
My issue is that I am unable to authenticate successfully against ServiceStack on my initial request to Account/LogOn.
ServiceStack related code in LogOn method of AccountController:
var apiAuthService = AppHostBase.Resolve<AuthService>();
apiAuthService.RequestContext = System.Web.HttpContext.Current.ToRequestContext();
var apiResponse = apiAuthService.Authenticate(new Auth
{
UserName = model.UserName,
Password = model.Password,
RememberMe = false
});
I have a custom Authentication Provider that subclasses CredentialsAuthProvider. I Configure as follows in the AppHost class:
var appSettings = new AppSettings();
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new ActiveDirectoryAuthProvider(),
}));
public override bool TryAuthenticate(ServiceStack.ServiceInterface.IServiceBase authService, string userName, string password)
{
//class to authenticate against ActiveDirectory
var adAuthentication = new ActiveDirectoryAuthenticationService();
if (!adAuthentication.Authenticate(userName, password))
return false;
var session = (CustomUserSession)authService.GetSession(false);
session.IsAuthenticated = true;
session.UserAuthId = session.UserAuthName;
authService.SaveSession(session, SessionExpiry);
return true;
}
I think my issue is that session.Id is null at this point and saving the session persists 'urn:iauthsession:' to the 'SessionCache'. However, I'm not sure how to correctly populate session.Id. Also, this may or may not be an issue, but the initial LogOn request is to Account/Logon which is handled by MVC. So, there is no request to ServiceStack prior to the AuthService.Authenticate() call in the AccountController.
A possible solution I came up with has been added below in my subclass of CredentialsAuthProvider.
public override bool TryAuthenticate(ServiceStack.ServiceInterface.IServiceBase authService, string userName, string password)
{
//class to authenticate against ActiveDirectory
var adAuthentication = new ActiveDirectoryAuthenticationService();
if (!adAuthentication.Authenticate(userName, password))
return false;
var session = (CustomUserSession)authService.GetSession(false);
//A possible solution???
if(session.Id == null)
{
var req = authService.RequestContext.Get<IHttpRequest>();
var sessId = HttpContext.Current.Response.ToResponse().CreateSessionIds(req);
session.Id = sessId;
req.SetItem(SessionFeature.SessionId, sessId);
}
//end possible solution
session.IsAuthenticated = true;
session.UserAuthId = session.UserAuthName;
authService.SaveSession(session, SessionExpiry);
return true;
}
Is there a configuration or call I'm missing to 'wire up' ServiceStack Authentication within MVC?
Thanks.
The only thing I am doing in my TryAuthenticate is validating the user name, password and returning true if valid.
I have another override method called OnAuthenticated where I am saving the session information. OnAuthenticated passes the Auth Service and the Session as parameter so you only have to:
public override void OnAuthenticated(IServiceBase authService, IAuthSession session,.....
{
session.IsAuthenticated = true;
session.....
authService.SaveSession(session, SessionExpiry);
}
This seems to store my session information as long as I registered the ICacheClient.