Sorry for the newbie question.
What the point of refresh token if i have to send my credentials anyway?
Or in another words, what the practical difference between requesting refresh token or just a new access token?
here is my code:
TokenResponse tokenResponse = null;
var tokenRequest = new TokenRequest
{
GrantType = "customgrant",
ClientId = "myUserName",
ClientSecret = "secret",
Address = disco.TokenEndpoint,
};
tokenResponse = await client.RequestTokenAsync(tokenRequest);
var testClient = new HttpClient();
testClient.SetBearerToken(tokenResponse.AccessToken);
var apiRes = await testClient.GetAsync("http://localhost:11000/api/GetData/104246");
if (!apiRes.IsSuccessStatusCode)
{
if (apiRes.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
RefreshTokenRequest refreshTokenRequest = new RefreshTokenRequest();
refreshTokenRequest.RefreshToken = tokenResponse.RefreshToken;
refreshTokenRequest.ClientId = "myUserName";
refreshTokenRequest.ClientSecret = "secret";
refreshTokenRequest.Address = disco.TokenEndpoint;
tokenResponse = await testClient.RequestRefreshTokenAsync(refreshTokenRequest);
}
}
Your question is not clear. refresh token is used to get new valid access token and you don't need user credentials, when you already have valid refresh token.
Related
The code below gets a token which I then use to try and fetch some data from an API which is behind AzureAD authentication.
I get a token back, but when I use it to try and reach the API, I get "login to your account" in apiResponse.
What is wrong with my authorization?
var recoAadAppId = "xxxxxxxxxxxxxx";
var callerAadAppId = "xxxxxxxxxxxxxx";
var callerAadTenantId = "xxxxxxxxxxxxxx";
var token = await AcquireTokenWithSecret(callerAadAppId, callerAadTenantId, recoAadAppId);
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(token.CreateAuthorizationHeader());
using (var response = await httpClient.GetAsync("https://redacted/app/rest/buildQueue"))
{
string apiResponse = await response.Content.ReadAsStringAsync();
}
public static Task<AuthenticationResult> AcquireTokenWithSecret(
string callerAadAppId, string callerTenantId, string recoAadAppId)
{
var secret = "mysecret";
var app = ConfidentialClientApplicationBuilder.Create(callerAadAppId).WithAuthority($"https://login.microsoftonline.com/{callerTenantId}").WithClientSecret(secret).Build();
var scopes = new[] { $"{recoAadAppId}/.default" };
return app.AcquireTokenForClient(scopes).ExecuteAsync(CancellationToken.None);
}
I have the following set up: Authorization server (.NET 6 with MVC, port 7000), Client (.NET 6 with MVC, port 7001), Resource Server (.NET 6 API, port 7002).
Authorization server set up:
builder.Services.AddAuthentication()
.AddGoogle(options =>
{
options.ClientId = builder.Configuration["ClientId"];
options.ClientSecret = builder.Configuration["ClientSecret"];
});
builder.Services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserNameClaimType = Claims.Name;
options.ClaimsIdentity.UserIdClaimType = Claims.Subject;
options.ClaimsIdentity.RoleClaimType = Claims.Role;
options.ClaimsIdentity.EmailClaimType = Claims.Email;
options.SignIn.RequireConfirmedAccount = false;
});
builder.Services.AddOpenIddict()
.AddCore(options =>
{
options.UseEntityFrameworkCore()
.UseDbContext<AuthorizationContext>();
})
.AddServer(options =>
{
options.SetAuthorizationEndpointUris("/connect/authorize")
.SetLogoutEndpointUris("/connect/logout")
.SetTokenEndpointUris("/connect/token")
.SetUserinfoEndpointUris("/connect/userinfo")
.SetIntrospectionEndpointUris("/connect/introspect");
options.RegisterScopes(Scopes.Email, Scopes.Profile, Scopes.Roles);
options.AllowAuthorizationCodeFlow();
options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();
options.UseAspNetCore()
.EnableAuthorizationEndpointPassthrough()
.EnableLogoutEndpointPassthrough()
.EnableTokenEndpointPassthrough()
.EnableUserinfoEndpointPassthrough()
.EnableStatusCodePagesIntegration();
})
.AddValidation(options =>
{
options.UseLocalServer();
options.UseAspNetCore();
});
builder.Services.AddHostedService<Worker>();
The seeded clients:
await manager.CreateAsync(new OpenIddictApplicationDescriptor
{
ClientId = "mvc",
ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654",
ConsentType = ConsentTypes.Explicit,
DisplayName = "MVC client application",
PostLogoutRedirectUris =
{
new Uri("https://localhost:7001/signout-callback-oidc")
},
RedirectUris =
{
new Uri("https://localhost:7001/signin-oidc")
},
Permissions =
{
Permissions.Endpoints.Authorization,
Permissions.Endpoints.Logout,
Permissions.Endpoints.Token,
Permissions.GrantTypes.AuthorizationCode,
Permissions.GrantTypes.RefreshToken,
Permissions.ResponseTypes.Code,
Permissions.Scopes.Email,
Permissions.Scopes.Profile,
Permissions.Scopes.Roles,
Permissions.Prefixes.Scope + "api1"
},
Requirements =
{
Requirements.Features.ProofKeyForCodeExchange
}
});
// resource server
if (await manager.FindByClientIdAsync("resource_server_1") == null)
{
var descriptor = new OpenIddictApplicationDescriptor
{
ClientId = "resource_server_1",
ClientSecret = "846B62D0-DEF9-4215-A99D-86E6B8DAB342",
Permissions =
{
Permissions.Endpoints.Introspection
}
};
await manager.CreateAsync(descriptor);
}
Client config:
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.LoginPath = "/login";
options.ExpireTimeSpan = TimeSpan.FromMinutes(50);
options.SlidingExpiration = false;
})
.AddOpenIdConnect(options =>
{
options.ClientId = "mvc";
options.ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654";
options.RequireHttpsMetadata = false;
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.ResponseType = OpenIdConnectResponseType.Code;
options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;
options.Authority = "https://localhost:7000/";
options.Scope.Add("email");
options.Scope.Add("roles");
options.Scope.Add("api1");
options.MapInboundClaims = false;
options.TokenValidationParameters.NameClaimType = "name";
options.TokenValidationParameters.RoleClaimType = "role";
});
Resource Server config:
builder.Services.AddOpenIddict()
.AddValidation(options =>
{
options.SetIssuer("https://localhost:7000/");
options.AddAudiences("resource_server_1");
options.UseIntrospection()
.SetClientId("resource_server_1")
.SetClientSecret("846B62D0-DEF9-4215-A99D-86E6B8DAB342");
options.UseSystemNetHttp();
options.UseAspNetCore();
});
builder.Services.AddAuthentication(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
This is how the client makes request to resource server:
[Authorize, HttpPost("~/")]
public async Task<ActionResult> Index(CancellationToken cancellationToken)
{
var token = await HttpContext.GetTokenAsync(CookieAuthenticationDefaults.AuthenticationScheme, OpenIdConnectParameterNames.AccessToken);
if (string.IsNullOrEmpty(token))
{
throw new InvalidOperationException("The access token cannot be found in the authentication ticket. " +
"Make sure that SaveTokens is set to true in the OIDC options.");
}
using var client = _httpClientFactory.CreateClient();
using var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:7002/api/message");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
using var response = await client.SendAsync(request, cancellationToken);
var content = await response.Content.ReadAsStringAsync();
response.EnsureSuccessStatusCode();
return View("Home", model: await response.Content.ReadAsStringAsync());
}
The problem is following when I set up those 3 instances (auth server, client, resource server) and I am NOT authenticated in the client (no cookies). I can authenticate on the client (and therefore on auth server). Then I make the request from the client to the resource server and it returns 200.
But then I stop all 3 instances and try to do it again.
At that time I'm already authenticated in the client (cookies) and can extract token (FYI the tokens are the same between requests before stopping instances and after). But this token is invalid and the response code from the resource server is 401.
On the resource server logs I can see the following logs: "OpenIddict.Validation.AspNetCore was not authenticated. Failure message: An error occurred while authenticating the current request", and "invalid_token, the specified token is invalid"
The question: is it expected behavior? I assume the reason is that data protection changed key ring or something like that. If it is expected - then how to do redeploys without reauthenticating all the users?
I'm pretty sure the problem is with this line
options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();
Those certificates (keys) are changed when you restart the application. You need to have production encryption/signing keys in place.
See options.AddEncryptionKey and options.AddSigningKey
Key can be created like this
var rsa = RSA.Create(2048);
var key = new RsaSecurityKey(rsa);
You can get the XML for the key and save it somewhere PRIVATE
var xml = key.Rsa.ToXmlString(true);
When you start the application, you load the key using the XML
var rsa = RSA.Create();
rsa.FromXmlString(xml);
Then you add the key to openiddict
options.AddEncryptionKey(rsa);
options.AddSigningKey(rsa);
You may also want to use the following methods instead
options.AddEncryptionCredentials
options.AddSigningCredentials
options.AddEncryptionCertificate
options.AddSigningCertificate
It depends of what you have available.
Considering PhoneNumber as my customized user's Username, I need only users whose phone number is verified to be able to Sign-Up. So I created a temp user and generated verification token for him/her and sent back the token to provided phone number as follows:
public Task Handle(SendSignupSmsRequest request)
{
var user = new CustomUser { UserName = request.PhoneNumber, PhoneNumber = request.PhoneNumber };
var token = await _userManager.GenerateUserTokenAsync(user, TokenOptions.DefaultPhoneProvider, "sign-up");
// send token to provided phone number
}
In sign-up request handler, I tried to re-create the same temp user and verify the token as follows:
public Task Handle(SignupRequest request)
{
var user = new CustomUser { UserName = request.PhoneNumber, PhoneNumber = request.PhoneNumber };
var tokenVerified = await _userManager.VerifyUserTokenAsync(user, TokenOptions.DefaultPhoneProvider, "sign-up", request.Token);
if (!tokenVerified)
// do something;
else
// do something else
}
I see that tokenVerified is always False! I tried the following to find what is wrong with my code:
Verify token with the same temp user ====> successful verification
var user = new CustomUser { UserName = request.PhoneNumber, PhoneNumber = request.PhoneNumber };
var token = await _userManager.GenerateUserTokenAsync(user, TokenOptions.DefaultPhoneProvider, "sign-up");
var tokenVerified = await _userManager.VerifyUserTokenAsync(user, TokenOptions.DefaultPhoneProvider, "sign-up", request.Token);
Verify token with the new temp user created just like temp user ====> unsuccessful verification
var user = new CustomUser { UserName = request.PhoneNumber, PhoneNumber = request.PhoneNumber };
var token = await _userManager.GenerateUserTokenAsync(user, TokenOptions.DefaultPhoneProvider, "sign-up");
user = new CustomUser { UserName = request.PhoneNumber, PhoneNumber = request.PhoneNumber };
var tokenVerified = await _userManager.VerifyUserTokenAsync(user, TokenOptions.DefaultPhoneProvider, "sign-up", request.Token);
It all comes down to SecurityStamp!
First, assign a temporal security stamp to user created in send sign-up sms request handler. Then in sign-up request handler set the same security stamp for the re-created user. Doing so, the token will be verified successfully.
Why context.SerializeTicket() and access_token not the same?
public override async Task CreateAsync(AuthenticationTokenCreateContext context)
{
//if (string.IsNullOrEmpty(context.Ticket.Identity.Name)) return;
var clientId = context.OwinContext.Get<string>("as:client_id");
var refreshTokenId = Guid.NewGuid().ToString("n");
var refreshToken = new RefreshToken
{
Id = refreshTokenId,
ClientId = clientId,
UserName = context.Ticket.Identity.Name,
IssuedUtc = DateTime.Now,
ExpiresUtc = DateTime.Now.AddDays(30)
};
context.Ticket.Properties.IssuedUtc = refreshToken.IssuedUtc;
context.Ticket.Properties.ExpiresUtc = refreshToken.ExpiresUtc;
refreshToken.ProtectedTicket = context.SerializeTicket();
await _cacheManager.SetAsync(refreshTokenId, refreshToken, TimeSpan.FromDays(10));
context.SetToken(refreshTokenId);
}
Result:
refreshToken.ProtectedTicket = "M2NQCH_kdzOJyHj9Sc-i_WjZtwS1Tqe3WjFXf-Laja80RUw_cOIZ9IH-Bhh3rlZx64lYxj6IUILJsBNG5FPMm7edaJqYA-qEsqYJjLTSUgSEIikeiomxcQA4qK2397HwhhzwA10QNMLFAUVscEE8MzJ4kGn_qanMP-xW3YVMycQLgGBjC5pDqBd8Q1U0nHQ5Cwe1OvWrngnq06Qe5QvZfRrMppw8MyyoliqRtPyq0UU";
Postman:
{"access_token":"jL1f5jI16pZpAmeNIvOCXe7zMEi1WT3ElOt1Tau-vfDV3iBNm6cH6oXz4XzLW2Y-2Me2Hyvl09R3vHWedlYUTiGAmFK4m75jomeqNbK2L9nnOv8-1N6iaEpB2ppT9bYLF9c77SMtmcQoLG0iBpdNu9fVObl5u5W7M5yxi6Kjq5MYdYLdzczfkaP3QA1csHdWeiuCEHf9Dw6F8XKYRpqFqUiSzFcqUW3qXcWrTF1Hfsk","token_type":"bearer","expires_in":2591999,"refresh_token":"05d7b5373b8c490fb136afe985d756ab"}
access_token is the token issued when the user is authenticated.
context.SerializeTicket() serializes the refresh token.
Why context. SerializeTicket ( ) and access_token not the same?
They are different because they represent different things.
[bonus]
refresh_token contains the key used to retrieve and deserialize the refresh token. This is done inside the Receive or ReceiveAsync method.
I'm getting a LinqToTwitter.TwitterQueryException "Bad Authentication Data" with innerException "The remote server returned an error: (400) Bad Request."
I'm using the latest version of LinqToTwitter (v2.1.06) and Twitter API v1.1.
The following code is used for authentication:
private XAuthAuthorizer GetAuthorizer()
{
var auth = new XAuthAuthorizer
{
Credentials = new XAuthCredentials
{
ConsumerKey = CONSUMER_KEY,
ConsumerSecret = CONSUMER_SECRET,
}
};
auth.Credentials.AccessToken = ACCESS_TOKEN;
auth.Credentials.OAuthToken = OAUTH_TOKEN;
auth.Authorize();
return auth;
}
And the error happens on the line of the foreach loop below:
XAuthAuthorizer _auth = GetAuthorizer();
_twitter = new TwitterContext(_auth);
var friendTweets = from tweet in _twitter.Status where tweet.Type == StatusType.Show && tweet.ID == tweetID select tweet;
foreach (var tweet in friendTweets)
{
AddTweetToCache(tweetID, tweet);
return tweet;
}
Any help would be greatly appreciated!
This fixed it. I was using the authentication method.
var auth = new ApplicationOnlyAuthorizer
{
Credentials = new InMemoryCredentials
{
ConsumerKey = CONSUMER_KEY,
ConsumerSecret = CONSUMER_SECRET
}
};