Blazor Server GraphServiceClient not authenticated in wrapper library - dependency-injection

I have a Blazor Server application that authenticates users through AAD. I'm using the Microsoft Graph SDK to query user info such as photo, etc., which I can do using DI to get the GraphServiceClient directly within a component in the Blazor Server app.
[Inject] private GraphServiceClient GraphServiceClient { get; set; }
However, for testing purposes, I've created a wrapper around the GraphServiceClient called IGraphService in another library within the same solution. This is where the problem occurs and the GraphServiceClient fails authentication with:
No account or login hint was passed to the AcquireTokenSilent call
My code is set up as follows:
Program.cs
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi(builder.Configuration.GetValue<string>("Graph:Scopes")?.Split(' '))
.AddMicrosoftGraph(builder.Configuration.GetSection("Graph"))
.AddInMemoryTokenCaches();
builder.Services.AddScoped<IGraphService, GraphService>();
appsettings.json
"AzureAd": {
"Instance": "https://login.microsoftonline.com",
"Domain": "{domain}.onmicrosoft.com",
"CallbackPath": "/signin-oidc",
"TenantId": "{tenantId}",
"ClientId": "{clientId}",
"ClientSecret": "{clientSecret}"
},
"Graph": {
"BaseUrl": "https://graph.microsoft.com/v1.0",
"Scopes": "user.read user.readbasic.all"
},
The part of the implementation of the GraphService I created to wrap the GraphServiceClient that tries to set up the GraphServiceClient:
return new GraphServiceClient(new DelegateAuthenticationProvider((requestMessage) =>
{
string[] scopes = new[] { "user.read", "user.readbasic.all" };
ITokenAcquisition tokenService = _contextAccessor.HttpContext.RequestServices.GetRequiredService<ITokenAcquisition>();
var token = tokenService.GetAccessTokenForUserAsync(scopes); <-- FAILS!!!
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Result);
return Task.FromResult(0);
}));
Clearing my cookies, this works fine the first time the app runs, but on restarts, it fails with the same message. Injecting the GraphServiceClient directly into a component works fine too, but I'm trying to wrap it so the components are loosely coupled.
Additionally, if I pass the AccessToken from the Blazor Server project to the wrapper library, it works fine too, but I want to avoid that.
What am I missing? Is my setup not right? Thanks for any help or pointers :)
Update 1
To simplify the issue, I have a component on a page and I can successfully request the user's token in that component, every time.
public sealed partial class HeaderBar
{
[Inject] private ITokenAcquisition TokenAcquisition { get; set; } = null!;
[Inject] private IGraphService GraphService { get; set; } = null!;
protected override async Task OnInitializedAsync()
{
try
{
// this works every time!
string token = await TokenAcquisition.GetAccessTokenForUserAsync(new[] { "user.read", "user.readbasic.all" });
var photo = await GraphService.GetUserPhoto();
...
}
catch (Exception ex)
{
...
}
}
}
As you can see, I'm not doing anything with the token, but if I remove that line, the same line within the GraphService fails. It will only succeed and get a token if I make the same call within the component first.
I've tried moving the code to the App.razor, but it fails there too.

Thanks to the following issue, I was able to pinpoint the problem and resolve it:
https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/issues/281
Problem:
When you lose the in-memory cache while your cookies are still alive, the auth calls are skipped and you don't bootstrap the token cache, which throws the MsalUIException described in the original question above. That explains why everything works the first time the app runs and doesn't when it gets restarted.
Some of the solutions describe using an attribute to re-populate the cache. However, in Blazor Server, there is no "Controller", so we can't use the AuthorizeForScopes attribute.
Solution:
I've created a page model for the standard _Host.csthml page that will attempt to get a token from the token cache, and if it's not populated, the AuthorizeForScopes attribute will do that for me (Thanks wmgdev!).
[AuthorizeForScopes(Scopes = new[] { "user.read", "user.readbasic.all" })]
public class _Host : PageModel
{
private readonly ITokenAcquisition _tokenAcquisition;
public _Host(ITokenAcquisition tokenAcquisition)
{
_tokenAcquisition = tokenAcquisition;
}
public async Task OnGetAsync()
{
// Get a token. If the token cache is not populated, the `AuthorizeForScopes` attribute will re-authorize, which will populate the cache
await _tokenAcquisition.GetAccessTokenForUserAsync(new[] { "user.read", "user.readbasic.all" });
}
}

Related

MSAL: Getting null everytime when AcquireTokenSilent in ASP.NET MVC Classic

I am working on ASP.Net MVC 4.7.2 (classics). Using Azure AD Authenticaiton (Microsoft Identity platform) for authentication and for Web Api authorization.
When using code flow to get the api resource I wan to store the token and get it silently when needed. I got the code from MSAL team from Git. But code doesn't work. Whenever I acquire the code silently the I get the error. When I debugged the issue I found that IAccounet is return null see the following line that returns null.
IAccount account =
_MsalAppBuilder.GetAccountAsync(ClaimsPrincipal.Current.GetAccountId()).Result;
Since account is null therefore the next line of code throws error. Therefore nt aoo us unable to work
AuthenticationResult result = app.AcquireTokenSilent(scopes, account).ExecuteAsync().Result;
As I debugged the issue, I could not find any reason why it is happening and even after extensive search. However what I found that in the Startup class, the method AcquireAccessToken never hit, thus the token does not save.
Can something help understand it please.
MSAL class:
public static class MsalAppBuilder
{
public static string GetAccountId(this ClaimsPrincipal claimsPrincipal)
{
string oid = claimsPrincipal.GetObjectId();
string tid = claimsPrincipal.GetTenantId();
return $"{oid}.{tid}";
}
private static IConfidentialClientApplication clientapp;
public static IConfidentialClientApplication BuildConfidentialClientApplication()
{
if (clientapp == null)
{
clientapp = ConfidentialClientApplicationBuilder.Create(Globals.clientId)
.WithClientSecret(Globals.clientSecret)
.WithRedirectUri(Globals.redirectUri)
.WithAuthority(new Uri(Globals.authority))
.Build();
// In-memory distributed token cache
clientapp.AddDistributedTokenCache(services =>
{
services.AddDistributedMemoryCache();
services.Configure<MsalDistributedTokenCacheAdapterOptions>(o =>
{
o.Encrypt = true;
});
});
}
return clientapp;
}
//this was commented already
/*
// Could also use other forms of cache, like Redis
// See https://aka.ms/ms-id-web/token-cache-serialization
clientapp.AddDistributedTokenCache(services =>
{
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost";
options.InstanceName = "SampleInstance";
});
});
*/
public static async Task RemoveAccount()
{
BuildConfidentialClientApplication();
var userAccount = await clientapp.GetAccountAsync(ClaimsPrincipal.Current.GetAccountId());
if (userAccount != null)
{
await clientapp.RemoveAsync(userAccount);
}
}
startup class:
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
//app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
// CookieManager = new SystemWebCookieManager()
AuthenticationType = "Cookies",
CookieManager = new Microsoft.Owin.Host.SystemWeb.SystemWebChunkingCookieManager()
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Sets the ClientId, authority, RedirectUri as obtained from web.config
ClientId = clientId,
Authority = authority,
RedirectUri = redirectUri,
// PostLogoutRedirectUri is the page that users will be redirected to after sign-out. In this case, it is using the home page
PostLogoutRedirectUri = redirectUri,
Scope = OpenIdConnectScope.OpenIdProfile,
// ResponseType is set to request the code id_token - which contains basic information about the signed-in user
//ResponseType = OpenIdConnectResponseType.CodeIdToken,
ResponseType = OpenIdConnectResponseType.CodeIdToken,
// OpenIdConnectAuthenticationNotifications configures OWIN to send notification of failed authentications to OnAuthenticationFailed method
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed
}
}
);
}
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
var scopes = Globals.scopeTravelAuthApi;
IConfidentialClientApplication clientApp = MsalAppBuilder.BuildConfidentialClientApplication();
AuthenticationResult result = await clientApp.AcquireTokenByAuthorizationCode(new[] { scopes}, context.Code).ExecuteAsync().ConfigureAwait(true);
context.HandleCodeRedemption(result.AccessToken, result.IdToken);
}
/// <summary>
/// Handle failed authentication requests by redirecting the user to the home page with an error in the query string
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
context.HandleResponse();
context.Response.Redirect("/?errormessage=" + context.Exception.Message);
return Task.FromResult(0);
}
}
By the way, I have checked all the settings related to Azure AD they ar correct. In the same app I am accessing Graph API which is working fine only issue is getting the access token silently.
My project is .ASP.Net 4.8. In order to fix the issue, I did not update the code, my code remain as is. I just upgraded the NuGet Packages whatever that make sence or needed to be upgraded for ASP.Net 4.8 but specifically the following:
Microsoft.Identity.Client, Microsoft.Identity.Client.Extensions.Msal, Microsoft.Identity.Web.TokenCache, and owin Nuget packages.

How to use OAuth with Swagger and NSwagStudio

I'm trying to generate a C# client for an API that has provided a swagger.json file to me, located at this link;
https://api.ekm.net/swagger/v1/swagger.json
Using the NSwagStudo application I am able to import the configuration file and generate a file called Client.cs which implements a class called Client and it has methods on it that match the API.
However when I call any of the methods I get an "Unauthorized" exception and I can not find any way to provide the OAuth key and secret to the client or anyone doing similar with other authentication methods.
Inspecting the swagger configuration files does show that OAuth is indicated as the authentication method as follows;
"securityDefinitions": {
"OAuth": {
"flow": "accessCode",
"authorizationUrl": "https://api.ekm.net/connect/authorize",
"tokenUrl": "https://api.ekm.net/connect/token",
"scopes": {
"tempest.customers.read": "Read a shop's customers.",
"tempest.customers.write": "Modify a shop's customers.",
"tempest.orders.read": "Read a shops orders.",
"tempest.orders.write": "Modify a shop's orders.",
"tempest.products.read": "Read a shop's products.",
"tempest.products.write": "Modify a shop's products.",
"tempest.categories.read": "Read a shop's categories.",
"tempest.categories.write": "Modify a shop's categories.",
"tempest.settings.orderstatuses.read": "Read a shop's order statuses.",
"tempest.settings.domains.read": "Read a shop's domains."
},
"type": "oauth2",
"description": "In order to ensure the safety of our users data, we require all partner applications to register via the [Partner Dashboard](https://partners.ekm.net/). Once registered, partners are provided with an application key, which can be used during an OAuth2 handshake to create a token. This token can then used to make requests on behalf of a merchant."
}
},
My test code is as follows;
static void Main(string[] args)
{
var swagClient = new Client();
var ords = swagClient.ApiV1OrdersGetAsync(1, 100).Result; // This call throws SwaggerException: Unauthorized
}
The Client class does not have any obvious methods or properties to set the security values or any constructor parameters.
Does anyone have an example of how to achieve this?
I agree. It's kind of strange that it doesn't just accept some kind of "insert JWT here".
Anyway, this is how I've fixed it:
Inject HttpClient
Tick on the box named "Inject HttpClient via constructor" in NSwagStudio
CustomMessageHandler
Introduce a custom HttpMessageHandler:
internal class AuthTokenHttpMessageHandler: HttpClientHandler
{
private readonly Action<HttpRequestMessage, CancellationToken> _processRequest;
public AuthTokenHttpMessageHandler(Action<HttpRequestMessage, CancellationToken> processRequest)
{
_processRequest = processRequest;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
_processRequest(request, cancellationToken);
return base.SendAsync(request, cancellationToken);
}
}
This handler accepts a delegate in which you can provide your JWT.
Integrating with your client
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
public class MyService : IDisposable
{
private readonly AuthTokenHttpMessageHandler _messageHandler;
private readonly HttpClient _httpClient;
private readonly MyNSwagClient _client;
public MyService()
{
_messageHandler = new AuthTokenHttpMessageHandler((req, _) =>
{
req.Headers.Authorization = new AuthenticationHeaderValue("bearer", "your token goes here");
});
_httpClient = new HttpClient(_messageHandler);
_client = new MyNSwagClient(_httpClient);
}
public async Task<SomeModel> GetStuffAsync(string paramenter1)
{
return await _client.StuffGetAsync(parameter1);
}
public void Dispose()
{
_httpClient?.Dispose();
_messageHandler?.Dispose();
}
}
I hope this helps you

ASP.net MVC: Accessing Google GoogleWebAuthorizationBroker returns access denied error

I'm trying to upload an video to my YouTube account with the following code in my ActionResult in my asp.net MVC project:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Upload([Bind(Include = " Title, Description")] HttpPostedFileBase uploadFile, Videos videos)
{
var credential = AuthYouTube();
YouTubeService youtubeService = new YouTubeService(new
YouTubeService.Initializer()
{
ApplicationName = "app-name",
HttpClientInitializer = credential
});
// Do Stuff with the video here.
}}
The AuthYouTube() looks like this (the same controller):
public UserCredential AuthYouTube()
{
string filePath = Server.MapPath("~/Content/YT/client_secret.json");
UserCredential credential;
try{
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
// This OAuth 2.0 access scope allows for full read/write access to the
// authenticated user's account.
new[] { YouTubeService.Scope.Youtube },
"username#domain.com",
CancellationToken.None,
new FileDataStore(Server.MapPath("~/Content/YT"),true)
).Result;
};
return credential;
}
catch(EvaluateException ex)
{
Console.WriteLine(ex.InnerException);
return null;
}
}
I have stored my client_secret.json that I downloaded from Google Developer Console inside the [project]/Content/YT. (Also tried inside the /App_Data folder.
When uploading the debugger is showing the folowwing message:
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.ComponentModel.Win32Exception: Access is denied
Place where the error occures:
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
StackStrace:
[Win32Exception (0x80004005): Access is denied]
Microsoft.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +115
Microsoft.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccess(Task task) +78
Google.Apis.Auth.OAuth2.<AuthorizeAsync>d__1.MoveNext() in C:\Users\mdril\Documents\GitHub\google-api-dotnet-client\Src\GoogleApis.Auth.DotNet4\OAuth2\GoogleWebAuthorizationBroker.cs:59
[AggregateException: One or more errors occurred.]
System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification) +4472256
Project.Controllers.VideosController.AuthYouTube() in d:\dev\Development\project\project\Controllers\VideosController.cs:133
project.Controllers.VideosController.Upload(HttpPostedFileBase uploadFile, Videos videos) in d:\dev\project\project\Controllers\VideosController.cs:71
What is the reason of this?
- Google API?
- folder / IIS rights?
Update 01-02-2016
Could it be some access error on the API side?
If not, could somebody please provide me the steps to grand the right IIS rights, still get the error after giving folder permissions.
Running the following code DOES create the folder as intended inside my App_Data, but also returns the same 'Access denied' error. The folder is empty.
var path = HttpContext.Current.Server.MapPath("~/App_Data/Drive.Api.Auth.Store");
// here is where we Request the user to give us access, or use the Refresh Token that was previously stored in %AppData%
UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets { ClientId = clientId, ClientSecret = clientSecret }
, scopes
, userName
, CancellationToken.None
, new FileDataStore(path,true)).Result;
Could somebody please explain how to get this working?
After ready the documentation again I found a way to get access to the API and upload my videos to YouTube. I hope I can clarify the way i did this.
How i did this:
https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#web-applications-aspnet-mvc
Create an callback controller:
using Google.Apis.Sample.MVC4;
namespace Google.Apis.Sample.MVC4.Controllers
{
public class AuthCallbackController : Google.Apis.Auth.OAuth2.Mvc.Controllers.AuthCallbackController
{
protected override Google.Apis.Auth.OAuth2.Mvc.FlowMetadata FlowData
{
get { return new AppFlowMetadata(); }
}
}
}
Create class and fill-in the credentials:
using System;
using System.Web.Mvc;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Auth.OAuth2.Flows;
using Google.Apis.Auth.OAuth2.Mvc;
using Google.Apis.YouTube.v3;
using Google.Apis.Util.Store;
namespace Google.Apis.Sample.MVC4
{
public class AppFlowMetadata : FlowMetadata
{
private static readonly IAuthorizationCodeFlow flow =
new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "PUT_CLIENT_ID_HERE",
ClientSecret = "PUT_CLIENT_SECRET_HERE"
},
Scopes = new[] { YouTubeService.Scope.YoutubeUpload },
DataStore = new FileDataStore(HttpContext.Current.Server.MapPath("~/App_Data/clientsecret.json")),
});
public override string AuthCallback
{
get { return #"/AuthCallback/Upload"; }
}
public override string GetUserId(Controller controller)
{
// In this sample we use the session to store the user identifiers.
// That's not the best practice, because you should have a logic to identify
// a user. You might want to use "OpenID Connect".
// You can read more about the protocol in the following link:
// https://developers.google.com/accounts/docs/OAuth2Login.
var user = controller.Session["user"];
if (user == null)
{
user = Guid.NewGuid();
controller.Session["user"] = user;
}
return user.ToString();
}
public override IAuthorizationCodeFlow Flow
{
get { return flow; }
}
}
}
In my ActionResult I set the YoutubeService. the creating of my video take place inside my Upload POST
Your own controller (mine is for the /upload action):
public async Task<ActionResult> Upload(CancellationToken cancellationToken)
{
var result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).AuthorizeAsync(cancellationToken);
if (result.Credential != null)
{
var youtubeService = new YouTubeService(new BaseClientService.Initializer
{
HttpClientInitializer = result.Credential,
ApplicationName = "name",
});
return View();
}
else
{
return new RedirectResult(result.RedirectUri);
}
}
For uploading logic see: https://developers.google.com/youtube/v3/code_samples/dotnet#upload_a_video
Set redirect URL in Google Developers console
In the Google Developers Console set the Authorized redirect URIs value to something like (my controller is called videos): http://www.domainname.com/Videos/Upload
**Using a single oAuth account **
Insted of saving the client id (GUID, see GetUserId inside AppFlowMetadata file) inside my session I now use one single id so I could use the same token/responsive for all the users.

Autofac dependency injection in implementation of OAuthAuthorizationServerProvider

I am creating a Web Api application and I want to use bearer tokens for the user authentication.
I implemented the token logic, following this post and everything seems to work fine.
NOTE: I am not using the ASP.NET Identity Provider. Instead I have created a custom User entity and services for it.
public class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureOAuth(app);
var config = new HttpConfiguration();
var container = DependancyConfig.Register();
var dependencyResolver = new AutofacWebApiDependencyResolver(container);
config.DependencyResolver = dependencyResolver;
app.UseAutofacMiddleware(container);
app.UseAutofacWebApi(config);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
public void ConfigureOAuth(IAppBuilder app)
{
var oAuthServerOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider()
};
// Token Generation
app.UseOAuthAuthorizationServer(oAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
and this is my implementation of the SimpleAuthorizationServerProvider class
private IUserService _userService;
public IUserService UserService
{
get { return (IUserService)(_userService ?? GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IUserService))); }
set { _userService = value; }
}
public async override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
var user = await UserService.GetUserByEmailAndPassword(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("role", "user"));
context.Validated(identity);
}
}
After I call the /token url, I receive the following error
No scope with a Tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested. This generally indicates that a component registered as per-HTTP request is being requested by a SingleInstance() component (or a similar scenario.) Under the web integration always request dependencies from the DependencyResolver.Current or ILifetimeScopeProvider.RequestLifetime, never from the container itself
Is there a way to use dependency injection inside this class? I am using a repository pattern to access my entities, so I don't think that it is a good idea to make a new instance of the object context. What is the correct way to do this?
I have had a similar problem.
The problem here is that when you try to inject IUserService in your provider, Autofac detects that it has been registered as InstancePerRequest (that uses the well-known lifetime scope tag 'AutofacWebRequest') but the SimpleAuthorizationServerProvider is registered in the 'root' container scope where the 'AutofacWebRequest' scope is not visible.
A proposed solution is to register dependencies as InstancePerLifetimeScope. This apparently solved the problem but introduces new ones. All dependencies are registered in the 'root' scope, that implies having the same DbContext and services instances for all the requests. Steven explains very good in this answer why is not a good idea to share the DbContext between requests.
After deeper investigation tasks, I've solved the problem getting the 'AutofacWebRequest' from the OwinContext in the OAuthAuthorizationServerProvider class and resolving the services dependencies from it, instead of letting Autofac to inject them automatically. For this I've used the OwinContextExtensions.GetAutofacLifetimeScope() extension method from Autofac.Integration.Owin, see example below:
using Autofac.Integration.Owin;
...
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
...
// autofacLifetimeScope is 'AutofacWebRequest'
var autofacLifetimeScope = OwinContextExtensions.GetAutofacLifetimeScope(context.OwinContext);
var userService = autofacLifetimeScope.Resolve<IUserService>();
...
}
I've made OAuthAuthorizationServerProvider registration and injection inside ConfigureOAuth method in a similar way than proposed by Laurentiu Stamate in another response to this question, as SingleInstance().
I've implemented RefreshTokenProvider in the same way.
EDIT
#BramVandenbussche, this is my Configuration method in the Startup class, where you can see the order of middlewares added to the OWIN pipeline:
public void Configuration(IAppBuilder app)
{
// Configure Autofac
var container = ConfigureAutofac(app);
// Configure CORS
ConfigureCors(app);
// Configure Auth
ConfigureAuth(app, container);
// Configure Web Api
ConfigureWebApi(app, container);
}
To use dependency injection in SimpleAuthorizationServerProvider you have to register IOAuthAuthorizationServerProvider to the Autofac container just like any other type. You can do something like this:
builder
.RegisterType<SimpleAuthorizationServerProvider>()
.As<IOAuthAuthorizationServerProvider>()
.PropertiesAutowired() // to automatically resolve IUserService
.SingleInstance(); // you only need one instance of this provider
You also need to pass the container to the ConfigureOAuth method and let Autofac resolve your instance like this:
var oAuthServerOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = container.Resolve<IOAuthAuthorizationServerProvider>()
};
You should always use single instances if your properties within the object don't change via external data (let's say you have a property which you set in the controller which dependents upon some information stored in the database - in this case you should use InstancePerRequest).
I also tried #jumuro answer using the OwinContextExtensions.GetAutofacLifetimeScope that saves my day. Instead of registering the IUserService at runtime, this answer gives an option on validation/creating the instance service after request.
I added some new answer because I can't comment yet because of my low reputations but added additional guide codes to help someone.
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
try
{
if (service == null)
{
var scope = Autofac.Integration.Owin.OwinContextExtensions.GetAutofacLifetimeScope(context.OwinContext);
service = scope.Resolve<IUserService>();
}
var user = await service.FindUserAsync(context.UserName);
if (user?.HashedPassword != Helpers.CustomPasswordHasher.GetHashedPassword(context.Password, user?.Salt))
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
}
catch(Exception ex)
{
context.SetError("invalid_grant", ex.Message);
return;
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
AuthenticationProperties properties = CreateProperties(context.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(identity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(identity);
}

How do I convert a refresh token to an access token using the LiveConnect API (C#)

I'm trying to create a LiveConnectClient with only a refresh token that was provided to me via asp.net identity (using OWIN) and the ProviderKey. It looks like the only way to do this without needing HttpContextBase is via InitializeSessionAsync.
When I try and create the client I'm getting:
Microsoft.Live.LiveAuthException: The user ID from the given RefreshTokenInfo instance does not match the refresh token.
Not really sure what user ID it is expecting as I'm giving it the provider key that was passed via ASP.NET Identity (17 chars in my case). Below is my code.
public class Class1
{
protected async Task<LiveConnectClient> GetLiveConnectClient()
{
var authClient = new LiveAuthClient(_clientId, _clientSecret, null, new RefreshTokenHandler(_refreshToken, _providerKey));
var session = await authClient.InitializeSessionAsync("http://.../signin-microsoft");
return new LiveConnectClient(session.Session);
}
}
public class RefreshTokenHandler : IRefreshTokenHandler
{
private readonly string _refreshToken;
private readonly string _userId;
public RefreshTokenHandler(string refreshToken, string userId)
{
_refreshToken = refreshToken;
_userId = userId;
}
public Task<RefreshTokenInfo> RetrieveRefreshTokenAsync()
{
return Task.FromResult(new RefreshTokenInfo(_refreshToken, _userId));
}
public async Task SaveRefreshTokenAsync(RefreshTokenInfo tokenInfo)
{
await Task.Delay(0);
}
}

Resources