پیاده سازی JSON Web Token با ASP.NET Web API 2.x
نویسنده: وحید نصیری
تاریخ: ۱۳۹۵/۰۴/۰۸ ۱۰:۴۵
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
PM> Install-Package Microsoft.Owin.Host.SystemWeb PM> Install-Package Microsoft.Owin.Security.Jwt PM> Install-Package structuremap PM> Install-Package structuremap.web
<appJwtConfiguration
tokenPath="/login"
expirationMinutes="2"
refreshTokenExpirationMinutes="60"
jwtKey="This is my shared key, not so secret, secret!"
jwtIssuer="http://localhost/"
jwtAudience="Any" /> <configSections>
<section name="appJwtConfiguration" type="JwtWithWebAPI.JsonWebTokenConfig.AppJwtConfiguration" />
</configSections> public interface ITokenStoreService
{
void CreateUserToken(UserToken userToken);
bool IsValidToken(string accessToken, int userId);
void DeleteExpiredTokens();
UserToken FindToken(string refreshTokenIdHash);
void DeleteToken(string refreshTokenIdHash);
void InvalidateUserTokens(int userId);
void UpdateUserToken(int userId, string accessTokenHash);
} public void CreateUserToken(UserToken userToken)
{
InvalidateUserTokens(userToken.OwnerUserId);
_tokens.Add(userToken);
} public interface IUsersService
{
string GetSerialNumber(int userId);
IEnumerable<string> GetUserRoles(int userId);
User FindUser(string username, string password);
User FindUser(int userId);
void UpdateUserLastActivityDate(int userId);
} public interface ISecurityService
{
string GetSha256Hash(string input);
} function doLogin() {
$.ajax({
url: "/login", // web.config --> appConfiguration -> tokenPath
data: {
username: "Vahid",
password: "1234",
grant_type: "password"
},
type: 'POST', // POST `form encoded` data
contentType: 'application/x-www-form-urlencoded' identity.AddClaim(new Claim(ClaimTypes.UserData, user.UserId.ToString()));
function doRefreshToken() {
// obtaining new tokens using the refresh_token should happen only if the id_token has expired.
// it is a bad practice to call the endpoint to get a new token every time you do an API call.
$.ajax({
url: "/login", // web.config --> appConfiguration -> tokenPath
data: {
grant_type: "refresh_token",
refresh_token: refreshToken
},
type: 'POST', // POST `form encoded` data
contentType: 'application/x-www-form-urlencoded' [JwtAuthorize(Roles = "Admin")] public class MyProtectedAdminApiController : ApiController
var jwtToken;
var refreshToken;
function doLogin() {
$.ajax({
// same as before
}).then(function (response) {
jwtToken = response.access_token;
refreshToken = response.refresh_token;
} function doCallApi() {
$.ajax({
headers: { 'Authorization': 'Bearer ' + jwtToken },
url: "/api/MyProtectedApi",
type: 'GET'
}).then(function (response) { function doLogout() {
$.ajax({
headers: { 'Authorization': 'Bearer ' + jwtToken },
url: "/api/user/logout",
type: 'GET' public AppOAuthProvider( Func<IUsersService> usersService, Func<ITokenStoreService> tokenStoreService, ISecurityService securityService, IAppJwtConfiguration configuration)
$.ajax({
headers: { 'Authorization': 'Bearer ' + jwtToken },
var bar; typeof bar; // "undefined"
if(jwtToken == null) // will check if the value is undefined or null.
{
alert('you need to login first');
return;
} <appJwtConfiguration
tokenPath="/login"
/> Install-Package Microsoft.Owin.Cors
public void Configuration(IAppBuilder app)
{
app.UseCors(CorsOptions.AllowAll);
app.UseOAuthAuthorizationServer(SmObjectFactory.Container.GetInstance<AppOAuthOptions>());
app.UseJwtBearerAuthentication(SmObjectFactory.Container.GetInstance<AppJwtOptions>());
} public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
// ...
}
} var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
config.MessageHandlers.Add(new PreflightRequestsHandler()); public class PreflightRequestsHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.Headers.Contains("Origin") && request.Method.Method == "OPTIONS")
{
var response = new HttpResponseMessage {StatusCode = HttpStatusCode.OK};
response.Headers.Add("Access-Control-Allow-Origin", "*");
response.Headers.Add("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization");
response.Headers.Add("Access-Control-Allow-Methods", "*");
var tsc = new TaskCompletionSource<HttpResponseMessage>();
tsc.SetResult(response);
return tsc.Task;
}
return base.SendAsync(request, cancellationToken);
}
} jQuery.support.cors = true;
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" }); var claimsIdentity = actionContext.RequestContext.Principal.Identity as ClaimsIdentity;
if (claimsIdentity == null)
{
// this is not our issued token
this.HandleUnauthorizedRequest(actionContext);
return;
} var userId = claimsIdentity.FindFirst(ClaimTypes.UserData).Value;
if (claimsIdentity == null || !claimsIdentity.Claims.Any())
{
// ...
}
data: {
username: "Vahid" ,
password: "1234" ,
grant_type: "password" } public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// how to get additional parameters
var form = await context.Request.ReadFormAsync();
var key1 = form["my-very-special-key1"];
<system.web>
<machineKey decryptionKey="Enter decryption Key here"
validation="SHA1"
validationKey="Enter validation Key here" />
</system.web>
protected static string GenerateKey(int length)
{
RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider();
byte[] buff = new byte[length];
rngCsp.GetBytes(buff);
StringBuilder sb = new StringBuilder(buff.Length * 2);
for (int i = 0; i < buff.Length; i++)
sb.Append(string.Format("{0:X2}", buff[i]));
return sb.ToString();
}
string validationKey = GenerateKey(64);
string decryptionKey = GenerateKey(32); public async Task<IHttpActionResult> LoginUserAsync(LoginUserBindingModel model)
{
var requestParams = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("username", model.Username),
new KeyValuePair<string, string>("password", model.Password)
};
var requestParamsFormUrlEncoded = new FormUrlEncodedContent(requestParams);
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:9577/");
client.DefaultRequestHeaders.Accept.Clear();
// client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await client.PostAsync("/login", requestParamsFormUrlEncoded);
if (response.StatusCode == HttpStatusCode.OK)
{
var responseString = await response.Content.ReadAsStringAsync();
var jsSerializer = new JavaScriptSerializer();
var responseData = jsSerializer.Deserialize<Dictionary<string, string>>(responseString);
return Content(HttpStatusCode.OK, responseData);
}
else
{
return BadRequest(response.ToString());
}
}
} The required anti-forgery cookie "__RequestVerificationToken" is not present.
public IHttpActionResult Get()
{
// Anonymous and Weakly-Typed Objects
return Ok(new
{
Id = 1,
Title = "Hello from My Protected Controller!",
Username = this.User.Identity.Name,
Property2 = new Object1 { id = 1, name = "V" }
});
// OR ... Strongly-Typed Objects
return Ok(model);
}
نمونه دیگر آن در یک برنامهی Angular هم در اینجا است و آن هم مشکلی ندارد.
urlSearchParams.append('grant_type', 'refresh_token');
urlSearchParams.append('refresh_token', model.refreshToken); $.ajax({
url: "/login", // web.config --> appConfiguration -> tokenPath
data: {
grant_type: "refresh_token",
refresh_token: refreshToken
},
type: 'POST', // POST `form encoded` data
contentType: 'application/x-www-form-urlencoded'
}) doRefreshToken(refreshToken: string): Observable<any> {
const body = new HttpParams()
.set('grant_type', "refresh_token")
.set('refresh_token', refreshToken);
return this.http.post('/login',
body.toString(),
{
headers: new HttpHeaders()
.set('Content-Type', 'application/x-www-form-urlencoded')
}
);
} private refreshToken(isAuthUserLoggedIn: boolean) {
const headers = new HttpHeaders({ "Content-Type": "application/x-www-form-urlencoded" });
const model = { refreshToken: this.tokenStoreService.getRawAuthToken(AuthTokenType.RefreshToken) };
body = new HttpParams();
body.set('grant_type', 'refresh_token');
body.set('refresh_token', model.refreshToken);
return this.http
(`${this.appConfig.basePath}${this.apiConfigService.configuration.loginPath}`,
body.toString(), { headers: headers })
.pipe(
map(response => response || {}),
catchError((error: HttpErrorResponse) => ErrorObservable.create(error)),
finalize(() => {
this.scheduleRefreshToken(isAuthUserLoggedIn);
})
)
.subscribe(result => {
console.log("RefreshToken Result", result);
this.tokenStoreService.storeLoginSession(result);
});
} body = new HttpParams();
body.set('grant_type', 'refresh_token');
body.set('refresh_token', model.refreshToken); Microsoft.AspNet.WebApi 5.2.5 Microsoft.Owin 4.0.0 Microsoft.Owin.Security 4.0.0 Microsoft.Owin.Host.SystemWeb 4.0.0 Microsoft.Owin.Security.Jwt 4.0.0 System.IdentityModel.Tokens.Jwt 5.2.2
var newIdentity = new ClaimsIdentity(context.Ticket.Identity);
var roleID = form.Result["roleID"];
newIdentity.AddClaim(new Claim("roleID", roleID));
newIdentity.AddClaim(new Claim("newClaim", "refreshToken"));
newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties);
context.Validated(newTicket); window.location = 'Index';
var claimsIdentity = this.User.Identity as System.Security.Claims.ClaimsIdentity; var userId = claimsIdentity.FindFirst(System.Security.Claims.ClaimTypes.UserData).Value;
<h2> <i>The resource cannot be found.</i> </h2></span> <b> Description: </b>HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly. <br><br> <b> Requested URL: </b>/login<br><br> [HttpException]: The controller for path '/login' was not found or does not implement IController.
{
"message": "Authorization has been denied for this request."
} if (!TokenStoreService().IsValidToken(accessToken, int.Parse(userId)))
{
// this is not our issued token
this.HandleUnauthorizedRequest(actionContext);
return;
} var claimsIdentity = actionContext.RequestContext.Principal.Identity as ClaimsIdentity;
if (claimsIdentity?.Claims == null || !claimsIdentity.Claims.Any())