Purpose:
I work with a third party that has implemented an OAuth2 authorization code flow. The goal is for an authenticated user in my app to be able to access areas of a this third parties app without having to sign in to the third party. I have provided the third party with our authorization url, a clientId and a callback url (not sure how this is used).
I am trying to wire up a basic authorization code flow using OAuth2, but I am stuck on an invalid_grant error and am having trouble determining how the process is intended to work.
Here is what I have implemented thus far:
In the startup class I configure my OAuth server to include an AuthorizeEndpointPath and an AuthorizationCodeProvider:
var allowInsecureHttp = bool.Parse(ConfigurationManager.AppSettings["AllowInsecureHttp"]);
var oAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = allowInsecureHttp,
TokenEndpointPath = new PathString("/oauth2/token"),
AuthorizeEndpointPath = new PathString("/oauth2/authorize"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
Provider = new CustomOAuthProvider(HlGlobals.Kernel),
AccessTokenFormat = new CustomJwtFormat(_baseUrl, HlGlobals.Kernel),
AuthorizationCodeProvider = new SimpleAuthenticationTokenProvider()
};
The CustomOAuthProider contains the following overrides:
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
//We validated that Client Id and redirect Uri are what we expect
if (context.ClientId == "123456" && context.RedirectUri.Contains("localhost"))
{
context.Validated();
}
else
{
context.Rejected();
}
return Task.FromResult<object>(null);
}
public override Task AuthorizeEndpoint(OAuthAuthorizeEndpointContext context)
{
var ci = new ClaimsIdentity("Bearer");
context.OwinContext.Authentication.SignIn(ci);
context.RequestCompleted();
return Task.FromResult<object>(null);
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
string clientId;
string clientSecret;
Guid clientIdGuid;
// Validate the context
context.Validated();
return Task.FromResult<object>(0);
}
Finally, the code in the SimpleAuthenticationTokenProvider is implemented as follows:
public SimpleAuthenticationTokenProvider() : base()
{
this.OnCreate = CreateCode;
this.OnReceive = ReceiveCode;
}
private readonly ConcurrentDictionary<string, string> _authenticationCodes = new ConcurrentDictionary<string, string>(StringComparer.Ordinal);
public void CreateCode(AuthenticationTokenCreateContext context)
{
context.SetToken(Guid.NewGuid().ToString("n") + Guid.NewGuid().ToString("n"));
_authenticationCodes[context.Token] = context.SerializeTicket();
}
public void ReceiveCode(AuthenticationTokenReceiveContext context)
{
string value;
_authenticationCodes.TryGetValue(context.Token, out value);
context.DeserializeTicket(value);
}
I have wired up a sample request inside of Postman. When I execute the postman oauth2 Authroization Code grant type the following methods from above fire sequentially:
ValidateClientRedirectUri
AuthorizeEndpoint
ValidateClientAuthentication
ReceiveCode
I am getting back an 'invalid_grant' error from postman, and no authorization code has been returned.
Can anyone point out where I may be going wrong here?
Secondly, how is the callbackurl/redirect url supposed to be used? Is it a fallback to the login page if the user has not authenticated?
UPDATE
I see that a 'code' is coming back in the querystring attached to the redirectURL on the response. Should the redirectURL be a page on the third-party site?
How is this code exchanged for a token?
UPDATE 2
Another question: At what phase do I read/parse the code? I can see the code coming in on the request querystring in my GrantClientCredentials method. Should I just parse it from the querystring and validate? At what point should ReceiveCode be called?
Related
I'm Providing an 'OAuthHandler' for Walmart and overriding some OAuthHandler methods to communicate between User Agent (Client) and Remote Authenticate Server.
Below is my controller:
[AllowAnonymous]
public class WalmartLoginController : Controller
{
public async Task<IActionResult> Login([FromForm] string provider)
{
if (string.IsNullOrWhiteSpace(provider))
{
return BadRequest();
}
if (!await HttpContext.IsProviderSupportedAsync(provider))
{
return BadRequest();
}
return Challenge(new AuthenticationProperties { RedirectUri = "/" }, provider);
}
[HttpGet("~/signout")]
[HttpPost("~/signout")]
public IActionResult SignOutCurrentUser()
{
return SignOut(new AuthenticationProperties { RedirectUri = "/" },
CookieAuthenticationDefaults.AuthenticationScheme);
}
}
And I added some classes to handle my requests: [here is the importance of those that overrode from the OAuthHandler class]
public partial class WalmartAuthenticationHandler : OAuthHandler<WalmartAuthenticationOptions>
{
public WalmartAuthenticationHandler(
[NotNull] IOptionsMonitor<WalmartAuthenticationOptions> options,
[NotNull] ILoggerFactory logger,
[NotNull] UrlEncoder encoder,
[NotNull] ISystemClock clock)
: base(options, logger, encoder, clock)
{}
// STEP 1: CREATE CHALLENGE URL
protected override string BuildChallengeUrl([NotNull] AuthenticationProperties properties, [NotNull] string redirectUri)
{
var scopeParameter = properties.GetParameter<ICollection<string>>(OAuthChallengeProperties.ScopeKey);
var scope = scopeParameter != null ? FormatScope(scopeParameter) : FormatScope();
var parameters = new Dictionary<string, string?>
{
["client_id"] = Options.ClientId,
["response_type"] = "code",
["scope"] = scope
};
if (Options.UsePkce)
{
var bytes = BitConverter.GetBytes(256 / 8);
var codeVerifier = WebEncoders.Base64UrlEncode(bytes);
// Store this for use during the code redemption.
properties.Items.Add(OAuthConstants.CodeVerifierKey, codeVerifier);
var challengeBytes = SHA256.HashData(Encoding.UTF8.GetBytes(codeVerifier));
var codeChallenge = WebEncoders.Base64UrlEncode(challengeBytes);
parameters[OAuthConstants.CodeChallengeKey] = codeChallenge;
parameters[OAuthConstants.CodeChallengeMethodKey] = OAuthConstants.CodeChallengeMethodS256;
}
var state = Options.StateDataFormat.Protect(properties);
parameters["redirect_uri"] = QueryHelpers.AddQueryString(redirectUri, "state", state);
return QueryHelpers.AddQueryString(Options.AuthorizationEndpoint, parameters);
}
// STEP 2 : CHANGE CODE WITH ACCESS_TOKEN
protected override async Task<OAuthTokenResponse> ExchangeCodeAsync([NotNull] OAuthCodeExchangeContext context)
{
var tokenRequestParameters = new Dictionary<string, string?>()
{
["client_id"] = Options.ClientId,
["client_secret"] = Options.ClientSecret,
["redirect_uri"] = context.RedirectUri,
["code"] = context.Code,
["grant_type"] = "authorization_code"
};
// Add CodeVerify to tokenRequestParameters
if (context.Properties.Items.TryGetValue(OAuthConstants.CodeVerifierKey, out var codeVerifier))
{
tokenRequestParameters.Add(OAuthConstants.CodeVerifierKey, codeVerifier);
context.Properties.Items.Remove(OAuthConstants.CodeVerifierKey);
}
var endpoint = QueryHelpers.AddQueryString(Options.TokenEndpoint, tokenRequestParameters);
using var request = new HttpRequestMessage(HttpMethod.Post, Options.TokenEndpoint);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));
request.Content = new FormUrlEncodedContent(tokenRequestParameters);
using var response = await Backchannel.SendAsync(request, Context.RequestAborted);
if (!response.IsSuccessStatusCode)
{
// An error occurred while retrieving an OAuth token.
}
var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync(Context.RequestAborted));
return OAuthTokenResponse.Success(payload);
}
// STEP 3: access to UserInformation with Access Token
protected override async Task<AuthenticationTicket> CreateTicketAsync(
[NotNull] ClaimsIdentity identity,
[NotNull] AuthenticationProperties properties,
[NotNull] OAuthTokenResponse tokens)
{
using var request = new HttpRequestMessage(HttpMethod.Get, Options.UserInformationEndpoint);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken);
using var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);
if (!response.IsSuccessStatusCode)
{
// An error occurred while retrieving the user profile.
}
using var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync(Context.RequestAborted));
var principal = new ClaimsPrincipal(identity);
var context = new OAuthCreatingTicketContext(principal, properties, Context, Scheme, Options, Backchannel, tokens, payload.RootElement.GetProperty("data"));
context.RunClaimActions();
await Events.CreatingTicket(context);
return new AuthenticationTicket(context.Principal!, context.Properties, Scheme.Name);
}
}
As you know, after running https:\\Development-Domain.com\signin by post method (second action), a Challenge is started and will redirect to BuildChallengeUrl() and return a Url with Code and State. The next action would be ExchangeCodeAsync() to change the Code sent by the remote server with an AccessToken.
The question is, which action or method was missed in this process? Will my ExchangeCodeAsync() call automatically after the BuildChallengeUrl() handler, or do I need to put some action to continue to authorize the user?
Update #1: I added a callback action to get info from 'QueryString' in controller to call next method of handler class:
[HttpPost("~/signin-oidc")]
public async Task<IActionResult> Callback([FromForm] string provider)
{
var code = Request.Query["code"];
var state = Request.Query["state"];
if (string.IsNullOrWhiteSpace(code))
{
return BadRequest();
}
if (!await HttpContext.IsProviderSupportedAsync(provider))
{
return BadRequest();
}
return {?};
}
What code must I write in {?} to request for Access Token from TokenEndpoint?
Except callback action other code are in-line with Authorisation code flow.
In PKCE Authorization code flow, you should match with flow mentioned here, that will help pass security validation / testing post production.
How it works, Authorization Flow
Answer to question,
CallBack action an endpoint action method of callback url should trigger and make post request to token endpoint of authorisation server with code received as query string in CallBack Url with Code Verifier created with Code Challenge and on successful post it will return access-token, refresh-token, id-token, .... based on your configuration. You will need to specify callback url also with client_id and client_secret in configuration.
re-arranging code based on this How it works, Authorization Flow will help.
After figuring out and researching Microsoft .Net Core OAuth libraries, I got the answer and I would like to share it with you:
As you know, we no need to write any code in callback action for running the next method in the WalmartAuthenticationHandler class because all the process is automatic and event-based and all of them are provided in the Microsoft OAuthHandler class. You need to write some code inside the Callback action just for registering User after the authentication process.
Even you can use Microsoft ExternalLogin Identity pages for the registration of an external User.
Also, I decided to create a project on GitHub and I'd like to share it here
OktaProvider, maybe everyone needs to add external authentication for non-famous companies.
I followed this tutorial: https://medium.com/#st.mas29/microsoft-blazor-web-api-with-jwt-authentication-part-1-f33a44abab9d
I downloaded the example: https://github.com/StuwiiDev/DotnetCoreJwtAuthentication/tree/Part2
I can see that the token is created but I don't understand how it is or should be saved on the client side as each time I access the SampleDataController, which has the Authorize tag, it returns a 401.
When calling and adding the token using Postman it works.
What am I missing for my user to be authenticated? Doesn't Microsoft.AspNetCore.Authentication.JwtBearer handle the client part (storing the token)?
What am I missing for my user to be authenticated? Doesn't Microsoft.AspNetCore.Authentication.JwtBearer handle the client part (storing the token)?
The JwtBearer runs on server side , it will only validate the authorization header of request, namely Authorization: Bearer your_access_token, and won't care about how you WebAssembly codes runs . So you need send the request with a jwt accessToken . Since the tutorial suggests you should use localStorage , let's store the accessToken with localStorage .
Because WebAssembly has no access to BOM yet, we need some javascript codes served as glue . To do that, add a helper.js under the JwtAuthentication.Client/wwwroot/js/ :
var wasmHelper = {};
wasmHelper.ACCESS_TOKEN_KEY ="__access_token__";
wasmHelper.saveAccessToken = function (tokenStr) {
localStorage.setItem(wasmHelper.ACCESS_TOKEN_KEY,tokenStr);
};
wasmHelper.getAccessToken = function () {
return localStorage.getItem(wasmHelper.ACCESS_TOKEN_KEY);
};
And reference the script in your JwtAuthentication.Client/wwwroot/index.html
<body>
<app>Loading...</app>
<script src="js/helper.js"></script>
<script src="_framework/blazor.webassembly.js"></script>
</body>
Now, let's wrap the javascript codes into C# . Create a new file Client/Services/TokenService.cs:
public class TokenService
{
public Task SaveAccessToken(string accessToken) {
return JSRuntime.Current.InvokeAsync<object>("wasmHelper.saveAccessToken",accessToken);
}
public Task<string> GetAccessToken() {
return JSRuntime.Current.InvokeAsync<string>("wasmHelper.getAccessToken");
}
}
Register this service by :
// file: Startup.cs
services.AddSingleton<TokenService>(myTokenService);
And now we can inject the TokenService into Login.cshtml and use it to save token :
#using JwtAuthentication.Client.Services
// ...
#page "/login"
// ...
#inject TokenService tokenService
// ...
#functions {
public string Email { get; set; } = "";
public string Password { get; set; } = "";
public string Token { get; set; } = "";
/// <summary>
/// response from server
/// </summary>
private class TokenResponse{
public string Token;
}
private async Task SubmitForm()
{
var vm = new TokenViewModel
{
Email = Email,
Password = Password
};
var response = await Http.PostJsonAsync<TokenResponse>("http://localhost:57778/api/Token", vm);
await tokenService.SaveAccessToken(response.Token);
}
}
Let's say you want to send data within FetchData.cshtml
#functions {
WeatherForecast[] forecasts;
protected override async Task OnInitAsync()
{
var token = await tokenService.GetAccessToken();
Http.DefaultRequestHeaders.Add("Authorization",String.Format("Bearer {0} ",token));
forecasts = await Http.GetJsonAsync<WeatherForecast[]>("api/SampleData/WeatherForecasts");
}
}
and the result will be :
Apologies in advance as this is somewhat responding to a previous answer, but I don't have the rep to comment on that.
If it helps anyone else who was similarly looking for a solution to using JWT in a Blazor app, I found #itminus answer incredibly useful, but it also pointed me to another course.
One problem I found was that calling FetchData.cshtml a second time would blow up when it tries to add the Authorization header a second time.
Instead of adding the default header there, I added it to the HttpClient singleton after a successful login (which I believe Blazor creates for you automatically). So changing SubmitForm in Login.cshtml from #itminus' answer.
protected async Task SubmitForm()
{
// Remove any existing Authorization headers
Http.DefaultRequestHeaders.Remove("Authorization");
TokenViewModel vm = new TokenViewModel()
{
Email = Email,
Password = Password
};
TokenResponse response = await Http.PostJsonAsync<TokenResponse>("api/Token/Login", vm);
// Now add the token to the Http singleton
Http.DefaultRequestHeaders.Add("Authorization", string.Format("Bearer {0} ", response.Token));
}
Then I realised, than as I'm building a SPA, so I didn't need to persist the token across requests at all - it's just in attached to the HttpClient.
The following class handle the login process on the client, storing the JWT token in local storage. Note: It is the developer responsibility to store the JWT token, and passes it to the server. The client (Blazor, Angular, etc.) does not do that for him automatically.
public class SignInManager
{
// Receive 'http' instance from DI
private readonly HttpClient http;
public SignInManager(HttpClient http)
{
this.http = http;
}
[Inject]
protected LocalStorage localStorage;
public bool IsAuthenticated()
{
var token = localStorage.GetItem<string>("token");
return (token != null);
}
public string getToken()
{
return localStorage.GetItem<string>("token");
}
public void Clear()
{
localStorage.Clear();
}
// model.Email, model.Password, model.RememberMe, lockoutOnFailure: false
public async Task<bool> PasswordSignInAsync(LoginViewModel model)
{
SearchInProgress = true;
NotifyStateChanged();
var result = await http.PostJsonAsync<Object>("/api/Account", model);
if (result)// result.Succeeded
{
_logger.LogInformation("User logged in.");
// Save the JWT token in the LocalStorage
// https://github.com/BlazorExtensions/Storage
await localStorage.SetItem<Object>("token", result);
// Returns true to indicate the user has been logged in and the JWT token
// is saved on the user browser
return true;
}
}
}
// This is how you call your Web API, sending it the JWT token for // the current user
public async Task<IList<Profile>> GetProfiles()
{
SearchInProgress = true;
NotifyStateChanged();
var token = signInManager.getToken();
if (token == null) {
throw new ArgumentNullException(nameof(AppState)); //"No token";
}
this.http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
// .set('Content-Type', 'application/json')
// this.http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
Profiles = await this.http.GetJsonAsync<Profile[]>("/api/Profiles");
SearchInProgress = false;
NotifyStateChanged();
}
// You also have to set the Startup class on the client as follows:
public void ConfigureServices(IServiceCollection services)
{
// Add Blazor.Extensions.Storage
// Both SessionStorage and LocalStorage are registered
// https://github.com/BlazorExtensions/Storage
**services.AddStorage();**
...
}
// Generally speaking this is what you've got to do on the client. // On the server, you've got to have a method, say in the Account controller, whose function is to generate the JWT token, you've to configure the JWT middleware, to annotate your controllers with the necessary attribute, as for instance:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
and so on...
Hope this helps...
I've been using the Antiforgery Cookie for .NET Core (https://learn.microsoft.com/en-us/aspnet/core/security/anti-request-forgery) and I'm always returning a new View after the login:
// - HomeController.cs
[HttpPost, ValidateAntiForgeryToken]
public async Task<IActionResult> Login()
{
ClaimsIdentity identity = new ClaimsIdentity("myAuthType");
ClaimsPrincipal principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync("myScheme", principal,
new AuthenticationProperties
{
ExpiresUtc = DateTime.UtcNow.AddMinutes(10),
AllowRefresh = true,
IsPersistent = true
});
return View();
}
The code is done so that each call/view generates a new X-XSRF-Cookie and sends to the browser, who sends the value back for some Api calls.
The code for that is:
// - Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAntiforgery(o => o.HeaderName = CrossSiteAntiForgery.Header);
services.AddMvc(o => {
o.Filters.AddService(typeof(CrossSiteAntiForgery));
});
}
// - CrossSiteAntiForgery.cs
public class CrossSiteAntiForgery : ResultFilterAttribute
{
public const string Cookie = "XSRF-TOKEN";
public const string Header = "X-XSRF-TOKEN";
private IAntiforgery _antiforgery;
public CrossSiteAntiForgery(IAntiforgery antiforgery)
{
_antiforgery = antiforgery;
}
public override void OnResultExecuting(ResultExecutingContext context)
{
AntiforgeryTokenSet tokens = _antiforgery.GetAndStoreTokens(context.HttpContext);
context.HttpContext.Response.Cookies.Append(Cookie, tokens.RequestToken, new CookieOptions() { HttpOnly = false });
}
}
But I wanted to login via Ajax just because. So I kept everything and changed just the Login:
[HttpPost("Api/User/Login"), ValidateAntiForgeryToken, Produces("application/json")]
public async Task<IActionResult> Login()
{
var userFromDB = DB.GetUser(1);
ClaimsIdentity identity = new ClaimsIdentity("myAuthType");
ClaimsPrincipal principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync("myScheme", principal,
new AuthenticationProperties
{
ExpiresUtc = DateTime.UtcNow.AddMinutes(10),
AllowRefresh = true,
IsPersistent = true
});
return Json(userFromDB);
}
However now I get errors (Bad Request) because the XSRF tokens don't match. After some (a lot actually) digging, I think I found the issue, the browser does receive the Set-Cookie header properly from the ajax and sets the new cookie value, but this value is invalid because it seems whenever the _antiforgery.GetAndStoreTokens(HttpContext) is called, it uses the current User to generate the token somehow.
When I return a new View, the Context is updated with the new (logged in) User, the token is generated and sent to the browser and everything works perfectly.
When I use ajax and call HttpContext.SignInAsync(), the current User hasn't been updated yet and so the generated token will be invalid for the logged in User.
// - Login()
var user = HttpContext.User;
await HttpContext.SignInAsync("Login", principal,
new AuthenticationProperties
{
ExpiresUtc = DateTime.UtcNow.AddMinutes(10),
AllowRefresh = true,
IsPersistent = true
});
var loggedInUser = HttpContext.User;
bool truth = user.Equals(loggedInUser); // - true
// - meaning anything that relies on the new logged in User is invalid from here on.
The error message in the .NET console is The provided antiforgery token was meant for a different claims-based user. I tried the suggestion here and it seems hacky, it works only for the second request onwards, meaning users would need to see a first "Bad Request" for every User change (Login and Logout).
How can I get/update the User after it has been logged in so the XSRF token generation works? Or, if there's a better solution, how can I solve this?
Edit: I'm working around it for now like this, in case anyone has the same issue:
public async Task<IActionResult> Login()
{
// - Login code omitted for brevity.
return RedirectToAction("LoginUnelegantWorkaround");
}
public IActionResult LoginUnelegantWorkaround()
{
var model = DB.FetchModelIWasGoingToReturnBefore();
return Json(model);
// - token is properly generated now since User has been updated, everything works
}
// - Same thing needed for Logout
I'm using owin for my app, Web Api2 for working with data and MVC5 for view.
static Startup()
{
PublicClientId = "self";
UserManagerFactory = () => new UserManager<User, int>(new UserRepository());
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Account/Token"),
AuthorizeEndpointPath = new PathString("/Account/ExternalLogin"),
Provider = new ApplicationOAuthProvider(PublicClientId, UserManagerFactory),
RefreshTokenProvider = new SimpleRefreshTokenProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(59),
AllowInsecureHttp = true,
};
}
When user is logged in through /Token?username=x&password=x&grant_type=password, it generates bearer token with refresh GUID for future refresh token /Token?grant_type=refresh_token, all is working okey.
My app can login through Facebook, etc, it's getting data I need from Facebook (email, etc) then I want to generate token with refresh GUID, problem is that for refreshing I need to pass data through /Token?username&password but my user doesn't have password, I tough to send additional parameter Scope and show what provider and provider key its using and to search in DB this user is registered and give him permission to generate token.
Is this good idea?
I found solution, without using garnt_type=password:
This is my refresh token class that AuthroizationServer is using
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
{
private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens =
new ConcurrentDictionary<string, AuthenticationTicket>();
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{.......}
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{.......}
}
My AccountController is initialized like this:
public AccountController()
: this(Startup.UserManagerFactory(), Startup.OAuthOptions.AccessTokenFormat)
{
}
public AccountController(UserManager<User, int> userManager, ISecureDataFormat<AuthenticationTicket> accessTokenFormat)
{
UserManager = userManager;
AccessTokenFormat = accessTokenFormat;
UserManager.UserValidator = new CustomUserValidator<User>(UserManager);
}
Now I need to create AuthenticationTokenCreateContext and send it to my RefreshTokenProvider so it will add Refresh Guid in _refreshTokens, so when I will send my refreshGuid it will be valid. Where I'm creating Identity for ExternalUser
ClaimsIdentity oAuthIdentity = await UserManager.CreateIdentityAsync(user,
OAuthDefaults.AuthenticationType);
AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.Id.ToString());
oAuthIdentity.AddClaim(new Claim("TokenGuid", Guid.NewGuid().ToString()));
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
await Startup.OAuthOptions.RefreshTokenProvider.CreateAsync(new AuthenticationTokenCreateContext(Request.GetOwinContext(), AccessTokenFormat, ticket));
Authentication.SignIn(properties, oAuthIdentity);
Now all my tokens can be refreshed, because I passed TokenGuid to RefreshTokenProvider, after that I'm creating token with that GUID and next time I will refresh it /Token?grant_type=refresh_token.....
P.S. If you need detailed help you can write me.
A few extra remarks:
If you're using the default accesstoken formats.
Remember to use for example: Startup.OAuthOptions.RefreshTokenFormat
instead of Startup.OAuthOptions.AccessTokenFormat
Otherwise the decryptionstage will throw an exception
To get the refresh token in de response of the getexternallogin you have to add a override in: OAuthAuthorizationServerProvider
public override Task AuthorizationEndpointResponse(OAuthAuthorizationEndpointResponseContext context)
{
var refreshToken = context.OwinContext.Authentication.AuthenticationResponseGrant.Properties.Dictionary["refresh_token"];
if (!string.IsNullOrEmpty(refreshToken))
{
context.AdditionalResponseParameters.Add("refresh_token", refreshToken);
}
return base.AuthorizationEndpointResponse(context);
}
I have successfully added OAuth to my WebAPI 2 project using OWIN. I receive tokens and can use them in the HTTP Header to access resources.
Now I want to use those tokens also on other channels for authentication that are not the standard HTTP requests that the OWIN template is made for. For example, I am using WebSockets where the client has to send the OAuth Bearer Token to authenticate.
On the server side, I receive the token through the WebSocket. But how can I now put this token into the OWIN pipeline to extract the IPrincipal and ClientIdentifier from it? In the WebApi 2 template, all this is abstracted for me, so there is nothing I have to do to make it work.
So, basically, I have the token as a string and want to use OWIN to access the user information encoded in that token.
Thank you in advance for the help.
I found a part of the solution in this blog post: http://leastprivilege.com/2013/10/31/retrieving-bearer-tokens-from-alternative-locations-in-katanaowin/
So I created my own Provider as follows:
public class QueryStringOAuthBearerProvider : OAuthBearerAuthenticationProvider
{
public override Task RequestToken(OAuthRequestTokenContext context)
{
var value = context.Request.Query.Get("access_token");
if (!string.IsNullOrEmpty(value))
{
context.Token = value;
}
return Task.FromResult<object>(null);
}
}
Then I needed to add it to my App in Startup.Auth.cs like this:
OAuthBearerOptions = new OAuthBearerAuthenticationOptions()
{
Provider = new QueryStringOAuthBearerProvider(),
AccessTokenProvider = new AuthenticationTokenProvider()
{
OnCreate = create,
OnReceive = receive
},
};
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
With a custom AuthenticationTokenProvider, I can retrieve all other values from the token early in the pipeline:
public static Action<AuthenticationTokenCreateContext> create = new Action<AuthenticationTokenCreateContext>(c =>
{
c.SetToken(c.SerializeTicket());
});
public static Action<AuthenticationTokenReceiveContext> receive = new Action<AuthenticationTokenReceiveContext>(c =>
{
c.DeserializeTicket(c.Token);
c.OwinContext.Environment["Properties"] = c.Ticket.Properties;
});
And now, for example in my WebSocket Hander, I can retrieve ClientId and others like this:
IOwinContext owinContext = context.GetOwinContext();
if (owinContext.Environment.ContainsKey("Properties"))
{
AuthenticationProperties properties = owinContext.Environment["Properties"] as AuthenticationProperties;
string clientId = properties.Dictionary["clientId"];
...
}
By default, OWIN use ASP.NET machine key data protection to protect the OAuth access token when hosted on IIS. You can use MachineKey class in System.Web.dll to unprotect the tokens.
public class MachineKeyProtector : IDataProtector
{
private readonly string[] _purpose =
{
typeof(OAuthAuthorizationServerMiddleware).Namespace,
"Access_Token",
"v1"
};
public byte[] Protect(byte[] userData)
{
throw new NotImplementedException();
}
public byte[] Unprotect(byte[] protectedData)
{
return System.Web.Security.MachineKey.Unprotect(protectedData, _purpose);
}
}
Then, construct a TicketDataFormat to get the AuthenticationTicket object where you can get the ClaimsIdentity and AuthenticationProperties.
var access_token="your token here";
var secureDataFormat = new TicketDataFormat(new MachineKeyProtector());
AuthenticationTicket ticket = secureDataFormat.Unprotect(access_token);
To unprotect other OAuth tokens, you just need to change the _purpose content. For detailed information, see OAuthAuthorizationServerMiddleware class here:
http://katanaproject.codeplex.com/SourceControl/latest#src/Microsoft.Owin.Security.OAuth/OAuthAuthorizationServerMiddleware.cs
if (Options.AuthorizationCodeFormat == null)
{
IDataProtector dataProtecter = app.CreateDataProtector(
typeof(OAuthAuthorizationServerMiddleware).FullName,
"Authentication_Code", "v1");
Options.AuthorizationCodeFormat = new TicketDataFormat(dataProtecter);
}
if (Options.AccessTokenFormat == null)
{
IDataProtector dataProtecter = app.CreateDataProtector(
typeof(OAuthAuthorizationServerMiddleware).Namespace,
"Access_Token", "v1");
Options.AccessTokenFormat = new TicketDataFormat(dataProtecter);
}
if (Options.RefreshTokenFormat == null)
{
IDataProtector dataProtecter = app.CreateDataProtector(
typeof(OAuthAuthorizationServerMiddleware).Namespace,
"Refresh_Token", "v1");
Options.RefreshTokenFormat = new TicketDataFormat(dataProtecter);
}
in addition to johnny-qian answer, using this method is better to create DataProtector. johnny-qian answer, depends on IIS and fails on self-hosted scenarios.
using Microsoft.Owin.Security.DataProtection;
var dataProtector = app.CreateDataProtector(new string[] {
typeof(OAuthAuthorizationServerMiddleware).Namespace,
"Access_Token",
"v1"
});
What is your token like, is it an encrypt string or a formatted string, what is it format?
I my code:
public static Action<AuthenticationTokenReceiveContext> receive = new Action<AuthenticationTokenReceiveContext>(c =>
{
if (!string.IsNullOrEmpty(c.Token))
{
c.DeserializeTicket(c.Token);
//c.OwinContext.Environment["Properties"] = c.Ticket.Properties;
}
});
The c.Ticket is always null.